修复File Provider Extension中EXC_RESOURCE内存超限(200MB)错误
修复File Provider Extension中PDF转图片的内存超限问题
问题原因
File Provider Extension的内存配额被严格限制在200MB以内,iOS16对这类扩展的内存管控更严苛。当PDF页面尺寸较大(比如A3幅面、高分辨率)时,直接渲染为位图会瞬间占用大量内存,超过配额触发崩溃。
具体修复方案
缩小渲染尺寸,降低单页内存占用
不要直接使用PDF页面的原始尺寸渲染,根据实际导出需求设置缩放比例,比如按屏幕分辨率或用户指定的质量等级缩小。位图内存占用和尺寸的平方成正比,缩小一半尺寸能减少75%的内存占用。
示例代码:let pageRect = page.getBoxRect(.mediaBox) // 按需设置缩放因子,比如缩放到原尺寸的0.5倍 let scaleFactor: CGFloat = 0.5 let renderSize = CGSize(width: pageRect.width * scaleFactor, height: pageRect.height * scaleFactor) let renderer = UIGraphicsImageRenderer(size: renderSize)整合旋转逻辑到渲染上下文,避免重复生成位图
原代码先渲染再旋转,会同时存在两个大尺寸UIImage对象,内存占用翻倍。可以在渲染PDF页面之前,直接在CGContext中应用旋转变换,一步生成正确方向的图片。
示例代码(替换原渲染块):var img = renderer.image { context in UIColor.white.set() context.fill(CGRect(origin: .zero, size: renderSize)) let cgContext = context.cgContext // 先平移到原点,再应用缩放 cgContext.translateBy(x: 0, y: renderSize.height) cgContext.scaleBy(x: scaleFactor, y: -scaleFactor) // 应用页面旋转角度 let rotationAngle = CGFloat(page.rotationAngle) * .pi / 180 if rotationAngle != 0 { cgContext.translateBy(x: pageRect.midX, y: pageRect.midY) cgContext.rotate(by: rotationAngle) cgContext.translateBy(x: -pageRect.midX, y: -pageRect.midY) } cgContext.drawPDFPage(page) }这样就不需要再调用
imageRotatedByDegrees,避免额外内存开销。及时释放内存,避免多页叠加占用
处理完单页后,立即将相关对象置为nil,触发ARC回收内存,不要让多个页面的图片同时留在内存中:// 写入文件后 img = nil page = nil跳过UIImage,直接将渲染结果写入文件
如果不需要UIImage对象做后续处理,可以直接用CGImageDestination将渲染结果写入JPEG文件,完全避免生成UIImage的内存开销:if let page = document.page(at: pageIndex) { let pageRect = page.getBoxRect(.mediaBox) let scaleFactor: CGFloat = 0.5 let renderSize = CGSize(width: pageRect.width * scaleFactor, height: pageRect.height * scaleFactor) // 创建CGContext let colorSpace = CGColorSpaceCreateDeviceRGB() guard let context = CGContext(data: nil, width: Int(renderSize.width), height: Int(renderSize.height), bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else { return } // 绘制背景 context.setFillColor(UIColor.white.cgColor) context.fill(CGRect(origin: .zero, size: renderSize)) // 变换上下文 context.translateBy(x: 0, y: renderSize.height) context.scaleBy(x: scaleFactor, y: -scaleFactor) let rotationAngle = CGFloat(page.rotationAngle) * .pi / 180 if rotationAngle != 0 { context.translateBy(x: pageRect.midX, y: pageRect.midY) context.rotate(by: rotationAngle) context.translateBy(x: -pageRect.midX, y: -pageRect.midY) } context.drawPDFPage(page) // 直接写入JPEG文件 if let cgImage = context.makeImage() { try? FileManager.default.createDirectory(at: imagePath.deletingLastPathComponent(), withIntermediateDirectories: true) if let destination = CGImageDestinationCreateWithURL(imagePath as CFURL, kUTTypeJPEG, 1, nil) { CGImageDestinationAddImage(destination, cgImage, [kCGImageDestinationLossyCompressionQuality: quality] as CFDictionary) CGImageDestinationFinalize(destination) } } // 释放资源 page = nil }
修改后的完整代码示例
if let page = document.page(at: pageIndex) { let pageRect = page.getBoxRect(.mediaBox) // 根据导出需求调整缩放因子,比如0.5~1.0之间 let scaleFactor: CGFloat = 0.5 let renderSize = CGSize(width: pageRect.width * scaleFactor, height: pageRect.height * scaleFactor) let renderer = UIGraphicsImageRenderer(size: renderSize) var img = renderer.image { context in UIColor.white.set() context.fill(CGRect(origin: .zero, size: renderSize)) let cgContext = context.cgContext // 缩放并翻转坐标系 cgContext.translateBy(x: 0, y: renderSize.height) cgContext.scaleBy(x: scaleFactor, y: -scaleFactor) // 应用页面旋转 let rotationAngle = CGFloat(page.rotationAngle) * .pi / 180 if rotationAngle != 0 { cgContext.translateBy(x: pageRect.midX, y: pageRect.midY) cgContext.rotate(by: rotationAngle) cgContext.translateBy(x: -pageRect.midX, y: -pageRect.midY) } cgContext.drawPDFPage(page) } let imageData = img.jpegData(compressionQuality: quality) do { try FileManager.default.createDirectory(at: imagePath.deletingLastPathComponent(), withIntermediateDirectories: true) if let data = imageData { try data.write(to: imagePath) } } catch { print("写入JPG失败: \(error.localizedDescription)") } // 及时释放内存 img = nil page = nil }
内容的提问来源于stack exchange,提问作者Nasser A
相关产品推荐
相关产品推荐

