【问题标题】:How can I lower memory climb when generating large PDF's [duplicate]生成大型PDF时如何降低内存攀升[重复]
【发布时间】:2013-01-19 10:04:14
【问题描述】:

可能重复:
Cannot create PDF document with 400+ pages on iOS

我的应用程序生成 PDF 文件。由于用户能够添加页面,这些 PDf 可能很大,可能不受限制,尽管通常约为。 10. 我遇到了 iPhone 4 用户在 PDF 生成阶段遇到崩溃的问题。一些调查工作表明该应用程序在 PDF 生成阶段内存不足,我收到内存不足警告,然后最终崩溃。如果我在 iPhone 5 上添加 50 多个页面,在 iPhone 4 上添加更多页面,而不是在预期的模拟器上,我可以重现该问题。

任何人都可以建议我如何在生成 PDf 文件时减少这种强烈的内存攀升和最终崩溃。

我研究过 SO:iPhone App Crashes due to Low Memory but works fine in simulatorQuartz PDF API Causing Out of Memory Crashes

@interface ICPDFPreviewController ()
@property (nonatomic, strong) Certificate *certificate;
@property (nonatomic, strong) NSData *pdfData;
@property (nonatomic) BOOL viewHasUnloaded;
- (void)generatePdf;
- (void)pdfDone:(NSData *)data;
- (NSData *)createPdfWithPages:(NSArray *)pages;
@end

@implementation ICPDFPreviewController
@synthesize certificate=_certificate;
@synthesize scrollView=_scrollView;
@synthesize webView=_webView;
@synthesize pdfData=_pdfData;
@synthesize viewHasUnloaded=_viewHasUnloaded;



- (void)generatePdf
 {
 NSMutableArray *pagesArray = [NSMutableArray array];

 if ([self.certificate.certificateType.title isEqualToString:@"Minor Works"]) {
[pagesArray addObject:[[ICPDFMinorWorksPage1 alloc] initWithCertificate:self.certificate]];
[pagesArray addObject:[[ICPDFMinorWorksPage2 alloc] initWithCertificate:self.certificate]];

} else if ([self.certificate.certificateType.title isEqualToString:@"EIC"]) {
[pagesArray addObject:[[ICPDFEICPage1 alloc] initWithCertificate:self.certificate]];
[pagesArray addObject:[[ICPDFEICPage2 alloc] initWithCertificate:self.certificate]];
[pagesArray addObject:[[ICPDFEICPage3 alloc] initWithCertificate:self.certificate]];
[pagesArray addObject:[[ICPDFEICPage4 alloc] initWithCertificate:self.certificate]];
[pagesArray addObject:[[ICPDFEICPage5 alloc] initWithCertificate:self.certificate]];
[self addDistributionBoardsToPagesArray:pagesArray];
ICPDFEICPageFinal *pageFinal = [[ICPDFEICPageFinal alloc] initWithCertificate:self.certificate];
pageFinal.pageNumber.text = [NSString stringWithFormat:@"%d", pagesArray.count+1];
[pagesArray addObject:pageFinal];

} else if ([self.certificate.certificateType.title isEqualToString:@"Domestic EIC"]) {
[pagesArray addObject:[[ICPDFDomesticEICPage1 alloc] initWithCertificate:self.certificate]];
[pagesArray addObject:[[ICPDFDomesticEICPage2 alloc] initWithCertificate:self.certificate]];
[pagesArray addObject:[[ICPDFDomesticEICPage3 alloc] initWithCertificate:self.certificate]];
[pagesArray addObject:[[ICPDFDomesticEICPage4 alloc] initWithCertificate:self.certificate]];
[self addDistributionBoardsToPagesArray:pagesArray];
[pagesArray addObject:[[ICPDFDomesticEICPageFinal alloc] initWithCertificate:self.certificate]];

} else if ([self.certificate.certificateType.title isEqualToString:@"EICR"]) {
[pagesArray addObject:[[ICPDFEICRPage1 alloc] initWithCertificate:self.certificate]];
[pagesArray addObject:[[ICPDFEICRPage2 alloc] initWithCertificate:self.certificate]];
[self addObservationsToPagesArray:pagesArray];
[self addDistributionBoardsToPagesArray:pagesArray];
[pagesArray addObject:[[ICPDFEICRInspection alloc] initWithCertificate:self.certificate]];
[pagesArray addObject:[[ICPDFEICRInspectionPage1 alloc] initWithCertificate:self.certificate]];
[pagesArray addObject:[[ICPDFEICRInspectionPage2 alloc] initWithCertificate:self.certificate]];
[pagesArray addObject:[[ICPDFEICRInspectionPage3 alloc] initWithCertificate:self.certificate]];
[pagesArray addObject:[[ICPDFEICRPageFinal alloc] initWithCertificate:self.certificate]];
 }

// Set page count on all pages
int pageNumber = 0;
for (ICCertificateComponent *page in pagesArray) {
page.pageNumber.text = [NSString stringWithFormat:@"%d", ++pageNumber];
page.pageCount.text = [NSString stringWithFormat:@"%d", pagesArray.count];
}

NSData *pdfData = [self createPdfWithPages:pagesArray];
[self performSelectorOnMainThread:@selector(pdfDone:) withObject:pdfData waitUntilDone:YES];

 }

- (void)pdfDone:(NSData *)data
{
self.pdfData = data;
[self.webView loadData:self.pdfData MIMEType:@"application/pdf" textEncodingName:@"utf-8"   baseURL:nil];
[ICUtils removeProgressView];
}

- (NSData *)createPdfWithPages:(NSArray *)pages
 {
// Creates a mutable data object for updating with binary data, like a byte array
NSMutableData *pdfData = [NSMutableData data];

 ICCertificateComponent *firstPage = [pages objectAtIndex:0];

UIGraphicsBeginPDFContextToData(pdfData, firstPage.contentView.bounds, nil);

 for (int i = 0; i < pages.count; i++) {
ICCertificateComponent *thisPage = [pages objectAtIndex:i];
UIGraphicsBeginPDFPageWithInfo(thisPage.contentView.bounds, nil);
    ////////////////////////////////////////////////////////////////////
   //tried adding this after research on SO, did not stop app crash
  // CGContextSetInterpolationQuality((__bridge CGContextRef)(thisPage), kCGInterpolationHigh);    CGContextSetRenderingIntent((__bridge CGContextRef)(thisPage), kCGRenderingIntentDefault);
 /////////////////////////////////////////////////////////////////////

CGContextRef pdfContext = UIGraphicsGetCurrentContext();
[thisPage.contentView.layer renderInContext:pdfContext];
}

UIGraphicsEndPDFContext();

return pdfData;
}

- (void)addDistributionBoardsToPagesArray:(NSMutableArray *)pagesArray
{
int pageCount = pagesArray.count;
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"createdAt"    ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil]; 
NSArray *boards = [self.certificate.distributionBoards   sortedArrayUsingDescriptors:sortDescriptors];
for (DistributionBoard *thisBoard in boards) {
DebugLog(@"Creating a board page");
ICPDFDistributionBoard *boardPage = [[ICPDFDistributionBoard alloc]   initWithDistributionBoard:thisBoard];
boardPage.pageNumber.text = [NSString stringWithFormat:@"%d", ++pageCount];
DebugLog(@"Page number is %d", pageCount);
[pagesArray addObject:boardPage];

NSSortDescriptor *circuitDescriptor = [[NSSortDescriptor alloc] initWithKey:@"createdAt" ascending:YES];
NSArray *circuitDescriptors = [[NSArray alloc] initWithObjects:circuitDescriptor, nil]; 
NSArray *circuits = [thisBoard.circuits sortedArrayUsingDescriptors:circuitDescriptors];

//int circuitCount = circuits.count;
ICPDFCircuitDetails *circuitDetails = boardPage.circuitDetails;

int circuitCount = 0;
for (Circuit *thisCircuit in circuits) {
    circuitCount++;
    if (circuitCount > 16) {
        // Add an extension page
        DebugLog(@"Adding an extension sheet");
        circuitCount = 1;
        ICPDFDistributionBoardExtension *boardExtension = [[ICPDFDistributionBoardExtension  alloc] initWithDistributionBoard:thisBoard];
        [pagesArray addObject:boardExtension];
        boardExtension.pageNumber.text = [NSString stringWithFormat:@"%d", ++pageCount];
        circuitDetails = boardExtension.circuitDetails;
     }
    NSString *key = [NSString stringWithFormat:@"circuitRow%d", circuitCount];
    ICCircuitRow *circuitRow = [circuitDetails valueForKey:key];
    [circuitRow populateFromCircuit:thisCircuit];
    }
  }
  }

【问题讨论】:

    标签: ios objective-c cocoa-touch memory-management


    【解决方案1】:

    一般来说,内存是有限的,而您生成的输出不是,因此使其工作的方法是确保:

    • 您在生成 PDF 时并未将整个 PDF 累积到内存中
    • 您不必担心每个页面的渲染副产品

    在您的情况下,使用 UIGraphicsBeginPDFContextToData 意味着您将整个 PDF 呈现为不断扩展的 NSData。当数据变得太大时,您将被杀死。相反,请尝试UIGraphicsBeginPDFContextToFile。同样在渲染页面的内部循环中,考虑插入一个@autoreleasepool { ... } 块,以防止对象在长期运行中不必要地堆积。我不确定您的 pagesArray 一堆东西实际上有多大,以及您是否可以考虑在生成时一次“分页”一页。

    【讨论】:

    • 感谢您对这里发生的事情的清晰详细的解释和建议,现在它更有意义了!当从 UIGraphicsBeginPDFContextToData 更改为 UIGraphicsBeginPDFContextToFile 时,我是否需要将 `NSMutableData *pdfData = [NSMutableData data]` 更改为 NSMutableString?
    • @JSA986:大概是的。流程取决于您要对数据执行的操作。看起来您想将其加载到 Web 视图中。在这种情况下,创建一个临时文件,生成该文件,然后加载带有路径的 Web 视图。您可以设计您的 -createPdfWithPages: 方法来接受所需输出文件的路径,这样您就可以保持目标的对称所有权,并在完成后将其删除。
    • 我认为这在我的情况下不起作用,我确实想将它正确加载到 webview 中,但如果我将它从 data 更改为 file 我会收到各种黄色警告三角形,没有 pdf 渲染。我已经按照建议添加了@autoreleasepool { ... },但显然基于您的 cmets 我需要做更多,在保持 UIGraphicsBeginPDFContextToData 的同时我还能做些什么我还没有编写应用程序的这一部分,我没有任何运气使用writing to file 或对此进行内存管理
    • @JSA986:你不能简单地加入新功能。您需要为临时文件创建一个合理的路径,将其提供给函数,然后将文件提供给 webview 以加载(并在完成后删除文件)。如果您不确定如何做这些事情,请在此处提出新问题,因为它们超出了此特定问题/答案/评论线程的范围。
    • 嗯,好的,我会在此基础上提出新问题,感谢您的建议
    【解决方案2】:

    编辑:我认为 Ben Zotto 的解决方案是可行的方法:使用 UIGraphicsBeginPDFContextToFile


    几个月前有人问过这个问题。我写了一个内存映射数据消费者,可能会有所帮助:

    https://gist.github.com/3748250

    它使用内存映射的 PDF 上下文而不是普通内存。

    【讨论】:

    • 看起来不错!感谢您提供该链接并分享您的代码
    • 在我看来,您甚至可以尝试创建一个只写入文件并检查您的内存使用情况的数据使用者。使用内存映射文件可能有点矫枉过正......
    • 好吧,我不知道数据消费者是什么。
    • 一个数据消费者是一个CGDataConsumerRef。它是 Core Graphics 中的一个抽象类。我想你可以切换到UIGraphicsBeginPDFContextToFile,你会没事的。如果没有,您可以重新访问此答案。
    • 我尝试切换到UIGraphicsBeginPDFContextToFile,但我的 pdf 在调试器中出现上下文错误,并且我收到大约 20 条警告,指出从函数 nsdata 类型返回字符串 _strong 的指针类型不兼容。
    猜你喜欢
    • 1970-01-01
    • 2018-08-28
    • 2017-07-18
    • 1970-01-01
    • 2014-03-30
    • 1970-01-01
    • 1970-01-01
    • 2022-01-16
    • 2022-01-14
    相关资源
    最近更新 更多