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
相关产品推荐
相关产品推荐

