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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 14:25:35