You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

修复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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 05:05:26