2013-02-05 37 views
5

生成大型PDF时,我的应用程序会向内存发出警告,然后在PDF生成过程中崩溃。该PDF被拉入一个Web视图,当网页获得一定量以上(取决于器件)我耗尽内存创建PDF时的内存警告和崩溃

我的研究此事至今让我明白,我需要:

改变UIGraphicsBeginPDFContextToDataIGraphicsBeginPDFContextToFile

  1. 创建一个合理的路径到一个临时文件,

  2. 给对函数,

  3. 将文件提交到webview加载。

  4. 完成后删除文件。

问题是,当我想我抓住它(只是)在pricipal,我不知道如何去acheiving这一点,或完全理解,以便我的代码内impliment它。在这个问题上非常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]; 
    } 
} 
} 

调试控制台的警告是这些负载,然后崩溃

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 
+0

包含了系统崩溃日志也 –

+0

我很抱歉,但我想我错过了一些东西。你想从什么生成一个PDF文件?你的来源是什么?一些图形上下文?你在哪里有记忆问题?生成文件或在webview中显示它时? –

回答

5

长我在一个完全不同的平台上生成PDF文件并遇到这种问题。解决方案是每次生成一个页面(在打开下一个成像之前关闭每个页面(不是文档))。这对我的许多页面文档造成了巨大的影响。看看你的代码,这是你在这里面临的同样的问题。实际上,你还有一个额外的问题,那就是你首先以图形方式在内存中建立所有的页面数据,然后在内存中建立所有的pdf页面,并且崩溃。请阅读下面有关如何解决问题的详细信息。

阅读here

看来,你想要的顺序是:

// 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(); 

所以这应该做的是在内存中呈现的数据量保持到一个页面在一个时间(加上一些开销对于文档全局数据)。

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

这里是关键 - 一次只绘制一页。你不会显示所有的ICPDFEICRPage1等,但是它们似乎是将页面内容呈现给某种UIView的对象?这意味着它们具有整个视图的像素数据(即复制1)。因此,您甚至可以在绘制任何PDF页面之前,为每个单独的页面使用内存。然后,您打开PDF页面上下文,将其呈现为NSMutableData(这是数据的第二个副本)。

另一个问题是:为什么你要渲染某种内存构造(视图),而不是在打印该页面时将其渲染到每个页面的pdf页面图形上下文中?我猜测,这是因为你有一个UIView层次结构的字段和所有在xib文件中定义的,所以你不必手动做所有的布局和字符串的绘制等。正确?

如果是这样,那么要做的事情就是让你的页面数组成为一个数组,作为你需要的对象的列表,而不是实际分配的对象本身。也许为每种可能类型的页面,观察,分发板等定义一个枚举。然后,您的页面数组只是一个整数数组(这些枚举按照您希望在文档中显示的顺序)。

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

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

这将有希望足以让你开始正确的轨道。噢,使用文件技术,您需要为其提供一个文件路径,然后将该路径作为文件:// url传递给要显示的webview。

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

+0

我曾在Google搜索中对此进行过调查,并不确定如何处理这些与我的代码有关的任何提示? – JSA986

+0

我用很多更具体的信息大大扩展了我的答案,所以如果你再读一遍,我相信你会发现它回答你的问题。 – Dad

+0

只是想说谢谢你的非常详细的答案,我仍然试图解决这个问题,尽管重读了你很多次的答案,但我无法理解它。是的即时通讯使用ARC,再次感谢 – JSA986

2

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

  • 创建文件
  • 添加内容[开始绘制在后UIGraphicsGetCurrentContext();方法
  • 查看在网页视图

从塔按钮动作开始

- (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]; 
} 

快乐编码:)

0

尝试使用@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]; 
    } 
} 
0

如果没有别的办法,你可以试试我的解决方案。它滚动浏览webView并一次添加一页到PDF。我无法以其他方式解决这个问题。 这个解决方案的背后是它使用了一个计时器,并且您将在生成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; 
} 
相关问题