PDF转图片并调整尺寸时出现内存泄漏致应用崩溃
PDF转图片处理大文件时内存崩溃问题
我在QOS为background的自定义队列中调用一个方法,将PDF每页转为UIImage并调整尺寸。处理超过90页的PDF时,应用因内存问题崩溃,调试器提示:
Message from debugger: Terminated due to memory issue
我的代码如下:
func convertPDFToImages(pdfURL: URL) -> ContiguousArray<UIImage>? { guard let pdfDocument = PDFDocument(url: pdfURL) else { return nil } var images: ContiguousArray<UIImage> = [] autoreleasepool { for pageNum in 0..<pdfDocument.pageCount { if let pdfPage = pdfDocument.page(at: pageNum) { let pdfPageSize = pdfPage.bounds(for: .mediaBox) let renderer = UIGraphicsImageRenderer(size: pdfPageSize.size) var image = renderer.image { ctx in UIColor.white.set() ctx.fill(pdfPageSize) ctx.cgContext.translateBy(x: 0.0, y: pdfPageSize.size.height) ctx.cgContext.scaleBy(x: 1.0, y: -1.0) pdfPage.draw(with: .mediaBox, to: ctx.cgContext) } image = scalePreservingAspectRatio(image, newWidth: newWidth) images.append(image) } } } return images } func scalePreservingAspectRatio(_ image: UIImage, newWidth: CGFloat) -> UIImage { let scaleFactor = newWidth / image.size.width let scaledImageSize = CGSize( width: newWidth, height: image.size.height * scaleFactor ) let renderer = UIGraphicsImageRenderer( size: scaledImageSize ) let scaledImage = renderer.image { _ in image.draw(in: CGRect( origin: .zero, size: scaledImageSize )) } return scaledImage }
我已经尝试过这些方案,但都没解决:
- 注释掉调用
scalePreservingAspectRatio的代码——仍报错 - 使用ContiguousArray替代Array——仍报错
- 为循环添加autoreleasepool——仍报错
我猜测问题出在多次创建UIGraphicsImageRenderer和中间image实例上。
解决思路与方案
1. 把autoreleasepool移到循环内部
你当前把整个循环放在单个autoreleasepool中,所有临时对象要等循环结束才会释放。将autoreleasepool放到循环内部,每次迭代后就能及时释放当前页的中间对象:
func convertPDFToImages(pdfURL: URL) -> ContiguousArray<UIImage>? { guard let pdfDocument = PDFDocument(url: pdfURL) else { return nil } var images: ContiguousArray<UIImage> = [] for pageNum in 0..<pdfDocument.pageCount { autoreleasepool { if let pdfPage = pdfDocument.page(at: pageNum) { let pdfPageSize = pdfPage.bounds(for: .mediaBox) let renderer = UIGraphicsImageRenderer(size: pdfPageSize.size) var image = renderer.image { ctx in UIColor.white.set() ctx.fill(pdfPageSize) ctx.cgContext.translateBy(x: 0.0, y: pdfPageSize.size.height) ctx.cgContext.scaleBy(x: 1.0, y: -1.0) pdfPage.draw(with: .mediaBox, to: ctx.cgContext) } image = scalePreservingAspectRatio(image, newWidth: newWidth) images.append(image) } } } return images }
2. 直接生成缩放后的图片,避免中间实例
当前流程是先生成原尺寸图片再缩放,产生两次大内存占用。可以直接计算缩放尺寸,通过上下文缩放直接绘制PDF页面,一步得到目标尺寸的图片:
func convertPDFToImages(pdfURL: URL) -> ContiguousArray<UIImage>? { guard let pdfDocument = PDFDocument(url: pdfURL) else { return nil } var images: ContiguousArray<UIImage> = [] for pageNum in 0..<pdfDocument.pageCount { autoreleasepool { guard let pdfPage = pdfDocument.page(at: pageNum) else { return } let originalBounds = pdfPage.bounds(for: .mediaBox) let scaleFactor = newWidth / originalBounds.size.width let scaledSize = CGSize(width: newWidth, height: originalBounds.size.height * scaleFactor) let renderer = UIGraphicsImageRenderer(size: scaledSize) let scaledImage = renderer.image { ctx in UIColor.white.set() ctx.fill(CGRect(origin: .zero, size: scaledSize)) // 先缩放上下文,再绘制PDF页面,直接得到缩放后的图片 ctx.cgContext.scaleBy(x: scaleFactor, y: scaleFactor) ctx.cgContext.translateBy(x: 0.0, y: originalBounds.size.height) ctx.cgContext.scaleBy(x: 1.0, y: -1.0) pdfPage.draw(with: .mediaBox, to: ctx.cgContext) } images.append(scaledImage) } } return images }
3. 降低图片色彩精度(可选)
如果内存压力仍然很大,可以通过调整UIGraphicsImageRenderer的格式减少内存占用:
let format = UIGraphicsImageRendererFormat.preferred() format.opaque = true // 不需要透明通道时开启,可减少内存 format.scale = UIScreen.main.scale // 也可根据需求降低scale,比如设为1.0 let renderer = UIGraphicsImageRenderer(size: scaledSize, format: format)
4. 分批处理+磁盘缓存(极端场景)
如果PDF页数极多,可考虑分批处理,每处理一定页数就将图片写入磁盘,清空数组释放内存,最后再从磁盘读取组装。此方法会增加IO操作,仅在内存无法容纳时使用。
内容的提问来源于stack exchange,提问作者nastassia
相关产品推荐
相关产品推荐

