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

使用PHPickerViewController选图预览时内存占用极高如何解决?

内存暴涨根本原因

你直接将全尺寸原始图片赋值给UI组件时,系统会将图片完整解码为位图存到内存,内存占用仅和图片像素数正相关,和图片的磁盘压缩大小(比如jpg/png的文件大小)无关。位图内存占用计算公式为:图片宽度像素 * 图片高度像素 * 4字节 / 1024 / 1024,比如一张12000px * 10000px的原图,解码后内存占用刚好接近480MB,和你观测的涨幅一致。UIImageView的显示尺寸不会影响解码后的内存占用,就算你用100pt*100pt的视图显示大尺寸原图,还是会占用全尺寸位图的内存。

最优修复方案
  • 优先对图片做下采样后再赋值给预览视图,只加载和预览视图尺寸匹配的像素到内存,不要直接加载全尺寸图片
  • 不要使用itemProvider.loadObject(ofClass: UIImage.self)读取PHPicker返回的结果,这个接口会默认加载全尺寸图到内存,改用ImageIO框架做目标尺寸下采样,效率更高内存占用更低

通用下采样工具方法

func downsampleImage(at url: URL, to targetSize: CGSize, scale: CGFloat = UIScreen.main.scale) -> UIImage? {
    let maxDimension = max(targetSize.width, targetSize.height) * scale
    let options: [CFString: Any] = [
        kCGImageSourceCreateThumbnailFromImageAlways: true,
        kCGImageSourceThumbnailMaxPixelSize: maxDimension,
        kCGImageSourceCreateThumbnailWithTransform: true
    ]
    guard let imageSource = CGImageSourceCreateWithURL(url as CFURL, nil),
          let cgImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) else {
        return nil
    }
    return UIImage(cgImage: cgImage, scale: scale, orientation: .up)
}

PHPicker回调正确写法示例

import UniformTypeIdentifiers // UTType需要依赖该框架

func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
    picker.dismiss(animated: true)
    // 替换为你实际的预览视图尺寸
    let previewTargetSize = CGSize(width: 300, height: 300)
    guard let result = results.first else { return }
    // 先获取图片文件路径,避免全尺寸数据加载到内存
    result.itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.image.identifier) { [weak self] url, error in
        guard let url = url, error == nil else { return }
        // 拷贝到自有沙盒目录,避免系统自动删除临时文件
        let tempDir = FileManager.default.temporaryDirectory
        let targetURL = tempDir.appendingPathComponent(UUID().uuidString + url.pathExtension)
        do {
            try FileManager.default.copyItem(at: url, to: targetURL)
            // 后台线程执行下采样,避免卡主线程
            DispatchQueue.global().async {
                let previewImage = downsampleImage(at: targetURL, to: previewTargetSize)
                DispatchQueue.main.async {
                    // 主线程赋值给预览视图
                    self?.previewImageView.image = previewImage
                    // 后续需要上传原图时直接读取targetURL路径即可,无需持有全尺寸UIImage
                }
            }
        } catch {
            // 自定义处理文件拷贝错误逻辑
        }
    }
}
额外优化建议
  • 如果是多选图片预览,采用懒加载机制,仅加载当前屏幕可见的图片预览,滑出屏幕的视图及时置空image属性释放内存
  • 上传原图时直接读取沙盒里存储的原图片文件路径即可,全程不需要持有全尺寸UIImage对象

内容的提问来源于stack exchange,提问作者OPS Klings

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 14:54:03