PSPDFKit can deal with very complex documents, but since iOS is a platform with restricted memory, and since rendering PDFs can be a memory-consuming task, there are certain limits to what you can do before the OS kills the app. The limits differ by device — an older device may have more trouble with a 50,000-page document than a newer device. However, we’re using a significant amount of custom code so that old devices still work fine.
Note that PSPDFKit has been designed to use a lot of memory for caching and to make things more fluid. We don’t know how much memory is available on an end user’s device, so PSPDFKit will adapt and restrict usage when we get low memory notifications. This is normal behavior and nothing you should be worried about. However, if you’re getting lots and lots of warnings and the system kills the app thereafter, you should look into the issue.
Here’s a checklist of what you can do if you get crashes related to memory pressure:
Are you using multiple
PDFViewControllers at once? Each controller takes a considerable amount of memory, so keeping multiple ones on the screen at the same time can put a lot of memory pressure on the system. We suggest not using more than two at the same time (e.g. two as a split-screen is OK, but showing four might be an issue)
PSPDFKit.SDK.shared[.lowMemoryMode] = truein your app delegate before you use any other PSPDFKit APIs. This will limit memory use in PSPDFKit at the expense of rendering speed. It’s often a workable solution though.
Ensure you don’t have other memory leaks or retain cycles in your app. Instruments is helpful in finding these.
Certain PDFs might be too complex to render. This is a rare issue, but if your PDF is really complex (although this doesn’t necessarily mean large), you might need to make your PDF simpler or split things across multiple pages.
Saving can take up a considerable amount of memory if you have a few thousand annotations (remember that links are annotations as well). If so, make sure that background saving is disabled.
Make sure you don’t create multiple instances of the same
Document. For complex documents, such objects manage a considerable amount of memory, and it’s wasteful to destroy/recreate them, especially if a document has many pages. It’s better to use a global manager or something like
PDFDocumentPickerController, which only creates instances if they don’t yet exist. Then again, complex documents might have lots and lots of annotations, so you might also have better results if you destroy any old documents once you no longer show them.
Documentwith only one file-based source takes up less memory than one having multiple files as its backend. Using
datais the worst thing, memory-wise. If you require data use because of encryption, use a
DataProvidingobject instead, where you can decrypt/make available only those parts that are currently needed.
Memory Use for Image Decompressing
Rendering images requires them to be decompressed into memory. Images in PDFs are usually stored as separate objects (called XObjects) and contain raw binary data. This data can be compressed as JPEG, JBIG2, JPEG2000/JPX, RAW, or various other supported formats). Before images can be rendered, this data needs to be loaded and decompressed into memory.
For example, consider an image that’s 4000×3000 pixels large and saved with colors. This would result in the following memory usage:
memory_used = 4 * width * height 4*4000*3000/1024 = ~45.77 MB.
Why 4? ARGB is the most common way to save images, so there are 4 bytes required for every pixel.
This is more of an issue on mobile devices, where no swap space exists. Thus, there’s a hard limit on the available amount of memory, which also has to be shared with other applications and the operating system itself. You can read more about the device hardware specifications in the Wikipedia iOS device listing article.
Apple’s iOS operating system closely monitors memory use and will terminate processes that cross the limit. Apple’s TN2151 explains the various reasons for processes to be jettisoned. There is no actual documentation on how much memory a single process can actually use. Apple relies on a system where low memory notifications are sent so the application can free up resources in such situations. This doesn’t work if the application requests a large block of memory at once, which is what’s required to decompress an image.
The actual amount of memory that can be used isn’t documented, but it varies based on the device and iOS version, as well as the app type. The limit is much lower for app extensions.
As an SDK, we set a moderate and safe limit based on experimentation and experience over the years. This can be tricky, as this also depends on your application code and how much memory is being used there. For example, if you use frameworks like SpriteKit or OpenGL, or if you use embedded web views, these also have a high memory cost.
Devices with HiDPI (Retina) screens have more pixels and, thus, have a higher memory. For example, most iPads have 2048×1536 as their resolution, so one fullscreen image requires 12 MB of memory.
If PSPDFKit detects an image that would require more memory than what is safe to be allocated, we log a warning instead of taking the risk of being jettisoned. Apple’s native iOS renderer takes no such precautions, and when trying to render a too-complex PDF, the application often just exits.
“Couldn’t load image: image size too big (X bytes, maximum allowed is X)”
Adobe Acrobat has an Optimize… feature that allows you to recompress images to take up less space. Note that the raw image size can still be very large, even though in the PDF, the image appears to be downscaled and small. Use Edit > Preflight… and then select the List page objects option.
For example, here’s a PDF that has an image with 4672×13495 pixels, taking up 4672134954 = 240,5 MB.
You can use the PDF Optimizer feature of Adobe Acrobat Pro (File > Save as Other > Optimized PDF…) to reduce the image size (ppi, pixels per inch). To learn more, read our Optimize PDF Documents for Mobile Rendering guide.