16

I am using the following pseudocode to generate a PDF document:

CGContextRef context = CGPDFContextCreateWithURL(url, &rect, NULL);

for (int i = 1; i <= N; i++)
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  CGContextBeginPage(context, &mediaBox);

  // drawing code

  CGContextEndPage(context);
  [pool release];
}

CGContextRelease(context);

It works very well with small documents (N < 100 pages), but it uses too much memory and crashes if the document has more than about 400 pages (it received two memory warnings before crashing.) I have made sure there were no leaks using Instruments. What's your advice on creating large PDF documents on iOS? Thanks a lot.

edit: The pdf creation is done in a background thread.

STW
  • 44,917
  • 17
  • 105
  • 161
TP.
  • 740
  • 7
  • 17

4 Answers4

6

Since you're creating a single document via CGPDFContextCreateWithURL the entire thing has to be held in memory and appended to, something that commonly (though I can't say for certain with iOS and CGPDFContextCreateWithURL) requires a full before and after copy of the document to be kept. No need for a leak to create a problem, even without the before-and-after issue.

If you aren't trying to capture a bunch of existing UIKit-drawn stuff -- and in your sample it seems that you're not -- use the OS's printing methods instead, which offer built-in support for printing to a PDF. UIGraphicsBeginPDFContextToFile writes the pages out to disk as they're added so the whole thing doesn't have to be held in memory at once. You should be able to generate a huge PDF that way.

Matthew Frederick
  • 22,245
  • 10
  • 71
  • 97
  • Is UIGraphicsBeginPDFContextToFile thread safe? It doesn't say in the docs but I will try it out. Thanks a lot! – TP. May 04 '11 at 08:26
  • It appears that it is not likely thread safe. From the overview: "**Important:** The UIKit classes are generally not thread safe. All drawing-related operations should be performed on your application’s main thread." http://developer.apple.com/library/ios/#documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GraphicsDrawingOverview/GraphicsDrawingOverview.html – Matthew Frederick May 04 '11 at 08:32
  • Yeah, I don't want it to block the main thread for a long time... So this may not work for me. sigh. – TP. May 04 '11 at 08:49
  • It's a good potential solution to your problem though. You might have to consider just putting up an activity indicator on the screen before you use UIGraphicsBeginPDFContextToFile and keep the user waiting. I notice Apple have done this a bit on their iPad GarageBand application. – Damien May 10 '11 at 10:46
  • Instead of an activity indicator, you could keep your UI responsive while drawing the PDF on the main thread. Register a runloop observer and do a small slice of drawing (one page?) each go through the runloop. This is basically cooperative time sharing of the main runloop. Since the runloop will continue to run, the interface should remain responsive. Alternatives to a runloop observer would be scheduling GCD tasks on the main queue or using an `NSTimer` to space out your drawing. – Jeremy W. Sherman May 12 '11 at 18:18
2

Probably not the answer you want to hear, but looking at it from another perspective.

Could you consider it as a limitation of the device?... First check the number of pages in the PDF and if it is too large, give a warning to the user. Therefore handling it gracefully.

You could then expand on this....

You could construct small PDF's on the iDevice and if the PDF is too large, construct it server-side the next time the iDevice has a net connection.

Alex KeySmith
  • 16,657
  • 11
  • 74
  • 152
1

What about using a memory mapped file to back your CG data consumer? Then it doesn't necessarily have to fit in RAM all at once.

I created an example here: https://gist.github.com/3748250

Use it like this:

NSURL * url = [ NSURL fileURLWithPath:@"pdf.pdf"] ;
MemoryMappedDataConsumer * consumer = [ [ MemoryMappedDataConsumer alloc ] initWithURL:url ] ;

CGDataConsumerRef cgDataConsumer = [ consumer CGDataConsumer ] ;

CGContextRef c = CGPDFContextCreate( cgDataConsumer, NULL, NULL ) ;
CGDataConsumerRelease( cgDataConsumer ) ;

// write your PDF to context `c`

CGPDFContextClose( c ) ;
CGContextRelease( c ) ;

return 0;
nielsbot
  • 15,922
  • 4
  • 48
  • 73
  • I want to generate PDF more than 60 Pages but crashing with memory raise . when i tried above after importing from GITHUB I see so many errors like below in Xcode 7.3 ----------------- MemoryMappedDataConsumer.m: Implicit declaration of function 'munmap' is invalid in C99 Declaration of 'munmap' must be imported from module 'Darwin.POSIX.sys.mman' before it is required Use of undeclared identifier 'PROT_WRITE' 'PROT_READ' 'MAP_SHARED'--------- – Sanju May 26 '16 at 12:19
1

If you allocate too much memory, your app will crash. Why is generating an unusually large PDF a goal? What are you actually trying to accomplish?

NSResponder
  • 16,861
  • 7
  • 32
  • 46
  • An user imported a 1500+ pages pdf document into my app, modified it using the app and wanted to export it, but my app crashed... – TP. May 04 '11 at 08:20
  • 1
    Could you start by taking the original PDF and just replacing the pages that have changed? – Jeremy W. Sherman May 12 '11 at 18:19