内存警告和创buildPDF时崩溃

当生成一个大的PDF时,我的应用程序会向内存发出一个警告,然后在PDF的生成过程中崩溃。 PDF被绘制成网页视图,当页面达到一定数量(取决于设备)时,内存不足

我对这个问题的研究到目前为止让我明白我需要:

UIGraphicsBeginPDFContextToData更改为IGraphicsBeginPDFContextToFile

  1. 创build一个合理的path到临时文件,

  2. 给那个函数,

  3. 将文件放到webview中加载。

  4. 完成后删除文件。

问题是,虽然我认为我只是在主要地方掌握它,但是我不知道如何去实现这个目标,或者完全理解它,以便在我的代码中实现它。 在这个问题上的build议非常appricated

我也打开任何其他的想法来阻止内存崩溃

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

debugging控制台警告是这些负载,然后崩溃

  2013-02-08 10:38:35.475 iCertifi[5772:907] Received memory warning. 2013-02-08 10:38:35.477 iCertifi[5772:907] <ICPDFPreviewController: 0x1eb28930> didReceiveMemoryWarning 

很久以前,在一个完全不同的平台上,我正在生成PDF文件,并遇到这种问题。 解决scheme是每次生成一个页面(closures每个页面(不是文档),然后打开下一个页面进行成像)。 这对我的许多页面文档有很大的改变。 看看你的代码,这是你在这里面临的同样的问题。 实际上,你还有一个额外的问题,就是你正在用graphics的方式在内存中build立所有的页面数据,然后在内存中build立所有的pdf页面,并且崩溃。 请阅读下面有关如何解决问题的详细信息。

在这里读

看来你想要的序列是:

 // start pdf document using default page size (CGRectZero) UIGraphicsBeginPDFContextToFile(pdfFileName, CGRectZero, nil); // then loop through your content drawing it one page at a time. for ( page p in pages ) // or whatever you are cycling over for page content { UIGraphicsBeginPDFPage(); // or UIGraphicsBeginPDFPageWithInfo // do your drawing to the page here by drawing to the current graphics context. // if you are setting up transforms and such } // close out the last page and the document both UIGraphicsEndPDFContext(); 

所以这个应该做的是一次把内存中的渲染数据保存到一个页面(加上一些文档全局数据的开销)。

好。 我查看了你的代码,看起来你至less在内存中至less有2个渲染页面的副本,如果我正确地理解它的话。

这是关键 – 一次只能画一页。 你不显示所有的ICPDFEICRPage1等,但是看起来它们是将页面内容呈现给某种UIView的对象。 这意味着他们有整个视图的像素数据(这是副本1)。 所以,你甚至在渲染任何pdf页面之前,都会使用每一页的内存。 然后你打开PDF页面上下文,将其呈现给NSMutableData (这是数据的第二个副本)。

另一个问题是:为什么你要渲染某种内存构造(视图),而不是只在每个页面的PDF页面graphics上下文中进行渲染? 我这是因为你有一个UIView层次结构的字段和所有在xib文件中定义的,所以你不必手动做所有的布局和string的绘制。 正确?

如果是这样的话,那么要做的事情就是让你的页面数组成为一个你想要的顺序的ICPDFEICR...对象列表,而不是实际分配的对象本身。 也许为每种可能的页面types,观察结果,发行版等定义一个枚举。 然后你的页面数组只是一个整数数组(这些枚举按你想在文档中显示的顺序)。

然后只分配当前页面的实际对象,将其映像到页面,然后在下一页之前释放它。

另外,我假设(希望!)你正在使用ARC,否则你在这段代码中有很多内存泄漏(!!!)。

这将有希望足以让你开始正确的轨道。 哦,用文件技术,你需要给它一个文件path,然后你只需将该path作为文件:// url传递给webview来显示。

虽然现在我想到了,为什么你使用UIWebView来显示PDF? 查看缩放PDF查看器示例,以find不尝试将整个PDF文档加载到内存中以显示它的替代方法。

可能是这可以帮助你解决问题。 请注意,代码包含

  • 创build一个文件
  • 添加内容[在UIGraphicsGetCurrentContext();之后开始绘制UIGraphicsGetCurrentContext(); 方法
  • 在webview中查看

从button操作开始

 - (IBAction)buttonPress:(id)sender { [self createPDFData:[self getPDFFileName]]; } -(NSString *)getPDFFileName { NSString * fileName = @"Info.PDF"; NSArray *arrayPaths = NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES); NSString * path = [arrayPaths objectAtIndex:0]; NSString *pdfFileName = [path stringByAppendingPathComponent:fileName]; NSLog(@"path : %@",path); NSLog(@"pdf file name : %@",pdfFileName); return pdfFileName; } -(void)showPDFFile { UIWebView * webView = [[UIWebView alloc] initWithFrame:CGRectMake(0,44,1024,748)]; NSURL *url = [NSURL fileURLWithPath:[self getPDFFileName]]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; [webView setScalesPageToFit:YES]; [webView loadRequest:request]; [self.view addSubview:webView]; } -(void)createPDFData:(NSString *)fileName{ CGRect pageFrame = CGRectMake(0,0,768,1024); UIGraphicsBeginPDFContextToFile(fileName, CGRectZero, nil);//This starts drawing a pdf to file named "filename" UIGraphicsBeginPDFPageWithInfo(pageFrame, nil);//For each new page call this UIGraphicsGetCurrentContext(); UIGraphicsBeginPDFPageWithInfo(pageFrame, nil);//Next or new Page NSString * timeLabel = [NSString stringWithFormat:@"Some text"]; [timeLabel drawInRect:CGRectMake(200, 200, 428, 44) withFont:[UIFont boldSystemFontOfSize:10] lineBreakMode:NSLineBreakByWordWrapping alignment:NSTextAlignmentLeft]; UIGraphicsEndPDFContext(); [self showPDFFile]; } 

快乐编码:)

尝试在您的周期中使用@autoreleasepool

 for (int i = 0; i < pages.count; i++) { // notice this line @autureleasepool { ICCertificateComponent *thisPage = [pages objectAtIndex:i]; UIGraphicsBeginPDFPageWithInfo(thisPage.contentView.bounds, nil); CGContextRef pdfContext = UIGraphicsGetCurrentContext(); [thisPage.contentView.layer renderInContext:pdfContext]; } } 

如果没有其他工作,你可以尝试我的解决scheme 它滚动浏览webView并一次添加一页到PDF。 我无法解决这个问题。 这个解决scheme的背后是它使用了一个计时器,并且在生成PDF的同时您将看到应用程序正在滚动。

代码如下:

 ... CGPoint savedContentOffset; CGRect savedFrame; NSMutableData * pdfData; int pdfOffset; NSTimer *timer; ... - (void)preparePdf { CGFloat height = aWebView.scrollView.contentSize.height; CGRect screenRect = [[UIScreen mainScreen] bounds]; int pageHeight = screenRect.size.height; savedContentOffset = aWebView.scrollView.contentOffset; savedFrame = aWebView.frame; pdfData=[NSMutableData data]; aWebView.scrollView.contentOffset = CGPointZero; aWebView.frame = CGRectMake(aWebView.frame.origin.x,aWebView.frame.origin.y,aWebView.frame.size.width,pageHeight); UIGraphicsBeginPDFContextToData(pdfData, aWebView.frame, nil); NSLog(@"pageHeight = %d", pageHeight); pdfOffset = 0; timer = [NSTimer scheduledTimerWithTimeInterval: 0.4 target: self selector: @selector(printPdfPage:) userInfo: nil repeats: YES]; } - (void) printPdfPage: (NSTimer*) timer { CGFloat height = aWebView.scrollView.contentSize.height; CGRect screenRect = [[UIScreen mainScreen] bounds]; int pageHeight = screenRect.size.height; NSLog(@"pdfOffset = %d", pdfOffset); UIGraphicsBeginPDFPage(); [aWebView drawViewHierarchyInRect:CGRectMake(0, 0, screenRect.size.width, pageHeight) afterScreenUpdates:YES]; pdfOffset += pageHeight; aWebView.scrollView.contentOffset = CGPointMake(0, pdfOffset); if (pdfOffset > height) { [timer invalidate]; NSLog(@"pdf data size: %ld", pdfData.length); [self emailPdf: pdfData]; } } - (void) emailPdf: (NSMutableData*) pdfData { // finally end the PDF context. UIGraphicsEndPDFContext(); aWebView.scrollView.contentOffset = savedContentOffset; aWebView.frame = savedFrame; MFMailComposeViewController *mailer = [[MFMailComposeViewController alloc] init]; mailer.mailComposeDelegate = self; [mailer setSubject:[@"iPad Screenshot " stringByAppendingString:[self getDate]]]; [mailer addAttachmentData:pdfData mimeType:@"application/pdf" fileName:[[self getDate] stringByAppendingPathExtension:@"pdf"]]; NSString *emailBody = @""; [mailer setMessageBody:emailBody isHTML:NO]; [self presentViewController:mailer animated:YES completion:nil]; if ([SVProgressHUD isVisible]) { [SVProgressHUD showSuccessWithStatus:@"Screenshot prepared."]; } pdfData = nil; timer = nil; }