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

Swift:获取相册所有图片时因内存问题崩溃,求解决方案

解决相册图片加载内存崩溃的问题

兄弟,这个Terminated due to memory issue我太熟了——你这是一次性把相册里所有图片都往内存里塞,不爆才怪!咱们直接说解决方案,都是实战里验证过的:

核心问题根源

你现在的代码应该是在枚举PHFetchResult的时候,同步或者不加限制地把每个PHAsset转成了UIImage存起来。但PHAsset只是相册资源的引用,真正的图片数据(尤其是原图)体积超大,几十上百张加起来内存直接爆表。

具体修复方案

1. 改用按需加载,别一次性全加载

如果是用UICollectionView/UITableView展示图片,只在cell即将出现在屏幕上时,再加载对应的图片。绝对不要提前把所有图片都加载到数组里。

2. 用PHCachingImageManager异步加载,指定合适的尺寸

一定要用系统提供的异步加载方法,并且绝对不要用PHImageManagerMaximumSize——这个会加载原图,内存直接炸。要根据你实际展示的尺寸(比如cell的大小)来指定targetSize,系统会自动压缩图片到合适大小。

3. 取消无效的图片请求

当cell滚出屏幕被复用时,一定要取消之前的图片请求,避免这些请求还在后台加载图片,白白占用内存。

4. 分批处理(如果需要批量操作图片)

如果不是展示,而是要批量处理所有图片(比如导出),那一定要分批处理,每处理一批就用autoreleasepool释放内存,给系统回收的机会。

修复后的代码示例

场景1:用UICollectionView展示相册图片

// 全局或类里定义缓存图片管理器
let imageManager = PHCachingImageManager()
var fetchResult: PHFetchResult<PHAsset>!

func fetchImagesFromLibrary() {
    let fetchOptions = PHFetchOptions()
    fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
    // 可以先设分页加载,比如每次20张,滚动到底部再加载更多
    // fetchOptions.fetchLimit = 20
    fetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions)
    collectionView.reloadData()
}

// CollectionView的cell加载逻辑
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCell", for: indexPath) as! ImageCell
    
    let asset = fetchResult.object(at: indexPath.item)
    // 取消cell之前的请求,避免无效加载
    if let previousRequestID = cell.imageRequestID {
        imageManager.cancelImageRequest(previousRequestID)
    }
    
    // 按cell的实际尺寸加载图片,乘以屏幕scale保证清晰度
    let targetSize = CGSize(width: cell.bounds.width * UIScreen.main.scale, 
                           height: cell.bounds.height * UIScreen.main.scale)
    let requestOptions = PHImageRequestOptions()
    requestOptions.deliveryMode = .fastFormat // 先加载低清图占位,再加载高清图,提升流畅度
    requestOptions.isNetworkAccessAllowed = true // 允许加载iCloud上的照片
    
    // 异步加载图片
    let requestID = imageManager.requestImage(for: asset, 
                                             targetSize: targetSize,
                                             contentMode: .aspectFill,
                                             options: requestOptions) { [weak cell] image, _ in
        guard let cell = cell else { return }
        cell.imageView.image = image
        cell.imageRequestID = nil
    }
    cell.imageRequestID = requestID
    
    return cell
}

// 自定义Cell类,处理复用逻辑
class ImageCell: UICollectionViewCell {
    @IBOutlet weak var imageView: UIImageView!
    var imageRequestID: PHImageRequestID?
    
    override func prepareForReuse() {
        super.prepareForReuse()
        imageView.image = nil
        // 复用前取消未完成的请求
        if let requestID = imageRequestID {
            imageManager.cancelImageRequest(requestID)
            imageRequestID = nil
        }
    }
}

场景2:批量处理所有相册图片(比如导出)

func batchProcessImages() {
    let fetchOptions = PHFetchOptions()
    fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
    let fetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions)
    
    // 分批处理,每批20张,避免内存暴涨
    let batchSize = 20
    let totalCount = fetchResult.count
    
    for batchStart in stride(from: 0, to: totalCount, by: batchSize) {
        let batchEnd = min(batchStart + batchSize, totalCount)
        
        autoreleasepool {
            for index in batchStart..<batchEnd {
                let asset = fetchResult.object(at: index)
                let options = PHImageRequestOptions()
                options.isSynchronous = true
                
                imageManager.requestImageData(for: asset, options: options) { data, _, _, _ in
                    // 处理图片数据,比如保存到本地,处理完就不要持有data
                    if let data = data {
                        try? data.write(to: URL(fileURLWithPath: "你的保存路径"))
                    }
                }
            }
        }
        
        // 给系统一点时间回收内存
        Thread.sleep(forTimeInterval: 0.1)
    }
}

最后再提几个注意点

  • 尽量不要持有UIImage的强引用太久,用完就及时置空
  • 如果用了第三方图片库(比如Kingfisher),也可以用它来封装PHAsset的加载,自带缓存和请求取消逻辑
  • 测试的时候用真机,模拟器的内存限制和真机不一样,真机更容易暴露问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 09:31:34