3

可能重复:
无法在 iOS 上创建超过 400 页的 PDF 文档

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

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

我研究过 SO:iPhone App 由于内存不足而崩溃,但在模拟器Quartz PDF API 中运行良好,导致内存不足崩溃

@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];
    }
  }
  }
4

2 回答 2

3

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

  • 在生成 PDF 时,您不会在内存中累积整个 PDF
  • 您不必为每个页面保留渲染的副产品

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

于 2013-02-04T17:05:29.940 回答
2

编辑:我认为 Ben Zotto 的解决方案是要走的路:使用UIGraphicsBeginPDFContextToFile.


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

https://gist.github.com/3748250

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

于 2013-02-04T17:53:35.623 回答