Languages/Objective-C2011.01.11 15:18
아이패드 보급이 늘어가면서 아이패드 위에 PDF 뷰어같은걸 구현하시는 분들도 많이 늘어가는 것 같군요. 그런 소프트웨어 구현할 때 가장 많이 사용하게 되는 것이 CGContextDrawPDFPage라는 함수죠. 이 함수는 현재의 드로잉 컨텍스트 위에 PDF 페이지를 뿌립니다.

이 함수의 한가지 문제는 호출할 때 마다 굉장히 많은 메모리를 계속 잡아먹는다는 점이죠. 그래서 PDF 파일을 열어놓은 상태로 모든 페이지를 전부 다 보게 되는 경우, 메모리 관련 경고가 뜨게 될 수 있습니다.

이 문제를 해결하는 한가지 방법은 메모리 점유율이 증가하거나 메모리 관련 경고가 뜨는 경우 (그러니까 다시 말해 메인 View Controller의 didReceiveMemoryWarning이 호출되는 경우) PDF 파일을 닫았다가 다시 여는 것이 되겠습니다. 

그런데 그런 코드를 작성하려면 PDF 파일에 대한 레퍼런스 (CGPDFDocumentRef)를 들고 있는 클래스를 어디 한 군데로 제한하는 것이 바람직하죠. 그럴 때에는 Singleton 패턴을 써야 한다는 것은 아마 많은 분들이 알고 계시겠습니다만...

그런데 그냥 CGPDFDocumentRef만 singleton 패턴으로 관리하면 땡인걸까요?

그렇지 않습니다. 한 쪽에서 이 패턴 객체를 통해 레퍼런스를 가져가서 열심히 뭔가 작업을 하고 있는데, 다른 한쪽에서 이 레퍼런스를 닫아버린다거나 하게 되면 프로그램이 당장 돌아가시게 되죠. 

그러니까 결국은 Singleton 패턴을 통해 CGPDFDocumentRef에 대한 접근 자체를 통제해야 합니다. 몇 가지 방법이 있겠습니다. 레퍼런스 카운터를 통해서 모두가 document ref를 전부 다 썼다는 것이 보장되기 전까지는 닫지 않는다거나 하는 것도 한가지 방법이죠.

저는 메모리를 얼마 이상 쓰지 않아야 한다는 요구조건이 있어서, 다음과 같은 싱글턴 클래스를 만들고 block을 통해서 해당 PDF 문서에 대한 작업을 구현하도록 하는 클래스를 만들어서 쓰고 있습니다. 소스코드를 보시면...

//

//  PDFSingleton.h

//  Muine

//

//  Created by bjlee on 11. 1. 11..

//  Copyright 2011 buggymind.com. All rights reserved.

//


#import <Foundation/Foundation.h>



@interface PDFSingleton : NSObject {


}


+ (NSUInteger) do:(NSUInteger(^)(CGPDFDocumentRef))blk;


@end



//

//  PDFSingleton.m

//  Muine

//

//  Created by bjlee on 11. 1. 11..

//  Copyright 2011 buggymind.com. All rights reserved.

//


#import "PDFSingleton.h"



@implementation PDFSingleton


static CGPDFDocumentRef _pdf = NULL;

static int requestCount = 0;


+ (NSUInteger) do:(NSUInteger(^)(CGPDFDocumentRef))blk {


@synchronized ( self ) {

if ( ++requestCount == 6 ) {

CGPDFDocumentRelease(_pdf);

_pdf = NULL;

requestCount = 1;

}

if ( _pdf == NULL ) {

CFURLRef pdfURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("xxx.pdf"), NULL, NULL);

_pdf = CGPDFDocumentCreateWithURL((CFURLRef)pdfURL);

CFRelease(pdfURL);

}


return blk(_pdf);

}

}



@end


PDF 문서 페이지가 여섯번 이상 요청되면 강제로 닫았다가 다시 열도록 했구요. 해당 pdf 문서에 대한 사용자 작업은 block으로 처리했습니다. 사용 예를 보시면...

// ctx의 선언은 이 위 어딘가에 있음 ㅎ

[PDFSingleton do:^(CGPDFDocumentRef pdf) {

CGPDFPageRef page = CGPDFDocumentGetPage(pdf, index);

// ...

CGContextDrawPDFPage(ctx, page);

return (NSUInteger)0;

}];


그런데 이 코드에는 문제가 있습니다. 뭘까요?

네. 어떤 한 쓰레드에서 해당 PDF 파일을 사용해 뭔가 심각하고 오래 걸리는 일을 하고 있으면, 다른쪽에서는 그 일이 끝날 때 까지 무작정 기다려야 합니다.

그러니 동시성을 활용해 캐싱이라던가 프리패칭(pre-fetching)같은 것을 하고 싶다면, 이렇게 하면 곤란하겠죠. 연습문제 삼아, 각자 이 문제를 고민해보시기 바랍니다. :-)
신고
Posted by 이병준

소중한 의견, 감사합니다. ^^