Swift 5如何在UICollectionView中加载7000张以上图片解决卡顿闪退问题
UICollectionView加载大量相册图片卡顿/闪退优化方案
现有代码核心问题
- 一次性发起数千张图片的同步加载请求,阻塞运行线程
- 将所有加载完成的UIImage全量存入内存数组,图片数量超过3000后内存占用远超iOS应用上限,直接触发闪退
- 图片请求配置不合理,同步+高质量格式请求进一步加剧性能消耗
具体优化方案
1. 取消全量预加载,改为按需加载
- 不要全量请求所有图片,数组仅存储
PHAsset对象,单张PHAsset内存占用仅几字节,存7000条也只有几十KB,完全无压力 - 仅在UICollectionView的
cellForItemAt方法中,针对当前要显示的cell发起对应单张图片的请求
2. 优化PHImageRequestOptions配置
// 全局复用同一个请求配置即可,不要每次请求重复创建 let requestOptions = PHImageRequestOptions() requestOptions.isSynchronous = false // 关闭同步,避免阻塞线程 requestOptions.deliveryMode = .opportunistic // 先返回低清缩略图,再返回高清图,提升响应速度 requestOptions.resizeMode = .fast requestOptions.isNetworkAccessAllowed = true
3. 使用PHCachingImageManager替代默认PHImageManager
系统自带的缓存管理类会自动管理图片缓存,还支持预加载功能:
// 全局初始化缓存管理器 let cachingImageManager = PHCachingImageManager() // 当滚动停止时,预加载当前可见区域前后10个cell的图片,提升滑动流畅度 func prefetchImages(for indexPaths: [IndexPath]) { let assets = indexPaths.map { self.assets[$0.item] } cachingImageManager.startCachingImages(for: assets, targetSize: CGSize(width:150, height: 200), contentMode: .aspectFill, options: requestOptions) } // 滑动过快时,取消已经滑出屏幕的cell的图片请求,避免无效资源占用 func cancelPrefetching(for indexPaths: [IndexPath]) { let assets = indexPaths.map { self.assets[$0.item] } cachingImageManager.stopCachingImages(for: assets, targetSize: CGSize(width:150, height: 200), contentMode: .aspectFill, options: requestOptions) }
4. 优化UICollectionView加载逻辑
- 实现
UICollectionViewDataSourcePrefetching协议,处理预加载和取消请求逻辑 - 滑动过程中降低图片请求优先级,滚动停止后再提升优先级加载高清图
- cell重用时清空旧的图片,取消之前绑定的请求,避免出现图片错位
5. 优化后核心代码
var assets: [PHAsset] = [] // 仅存PHAsset,不存UIImage let cachingImageManager = PHCachingImageManager() let requestOptions = PHImageRequestOptions() func grabPhotos(){ let options = PHFetchOptions() options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)] let assetsFetchResult = (collection == nil) ? PHAsset.fetchAssets(with: .image, options: options) : PHAsset.fetchAssets(in: collection!, options: nil) // 直接存储PHAsset数组,不要全量请求图片 assets = assetsFetchResult.objects(at: IndexSet(integersIn: 0..<assetsFetchResult.count)) // 初始化请求配置 requestOptions.isSynchronous = false requestOptions.deliveryMode = .opportunistic requestOptions.resizeMode = .fast requestOptions.isNetworkAccessAllowed = true DispatchQueue.main.async { self.categoryView.reloadData() self.activityIndicator.stopAnimating() } } // cellForItemAt中按需加载图片 func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PhotoCell", for: indexPath) as! PhotoCell let asset = assets[indexPath.item] // 先清空旧图片,避免重用错位 cell.imageView.image = nil // 请求当前cell需要的图片 cachingImageManager.requestImage(for: asset, targetSize: CGSize(width:150, height: 200), contentMode: .aspectFill, options: requestOptions) { image, _ in DispatchQueue.main.async { cell.imageView.image = image } } return cell }
内容的提问来源于stack exchange,提问作者Tanjima Kothiya
相关产品推荐
相关产品推荐

