Swift异步加载展示图片求助:相册与Facebook图片批量加载卡顿
解决大量图片加载卡顿与逐张展示问题
咱们先拆解下你当前代码里的核心问题,再一步步给出优化方案:
一、相册图片加载的问题根源
你的grabPhotos()方法里有两个致命的性能问题:
- 同步请求阻塞线程:设置了
requestOptions.isSynchronous = true,这会让循环里的每张图片请求都同步执行——数千张图片的话,主线程会被完全卡住,直到所有图片都加载完才能响应UI。 - 全量刷新CollectionView:每加载完一张图片就调用
reloadData(),这会让列表不断重新渲染所有cell,性能损耗极大,而且用户也看不到逐张加载的效果。 - 预加载所有图片到内存:把数千张图片都塞进
imageArray,不仅加载慢,还会造成巨大的内存压力,甚至可能触发内存警告。
二、修改后的相册加载方案
我们改成按需加载:先快速获取所有图片的元数据(PHAsset),然后在cell即将显示时,再异步请求对应图片,加载完就更新单个cell。
1. 先保存PHAsset列表(替换原有的imageArray)
// 定义数组保存相册图片的元数据,这个操作非常快 var assetsArray: [PHAsset] = [] func grabPhotos() { let fetchOptions = PHFetchOptions() fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] let fetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions) if fetchResult.count > 0 { // 把所有PHAsset快速存入数组,不会阻塞线程 assetsArray = fetchResult.objects(at: IndexSet(0..<fetchResult.count)) // 只刷新一次列表,快速显示占位图 DispatchQueue.main.async { self.collectionView.reloadData() } } else { print("You have no photos") DispatchQueue.main.async { self.collectionView.reloadData() } } }
2. 修改CollectionView的数据源方法
在cell显示时异步请求图片,同时处理cell复用的错乱问题:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return assetsArray.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "galleryCell", for: indexPath) as! GalleryCollectionViewCell // 先设置占位图,避免复用旧图片 cell.gelleryImages.image = UIImage(named: "placeholder") let asset = assetsArray[indexPath.row] let imgManager = PHImageManager.default() let requestOptions = PHImageRequestOptions() requestOptions.isSynchronous = false // 必须异步请求 requestOptions.deliveryMode = .fastFormat // 可选:先加载低清图快速占位,再替换高清图 requestOptions.isNetworkAccessAllowed = true // 允许加载iCloud中的图片 // 给cell打标记,防止滚动时复用cell导致图片错乱 cell.tag = indexPath.row imgManager.requestImage(for: asset, targetSize: CGSize(width: 200, height:200), contentMode: .aspectFill, options: requestOptions) { [weak self] (image, error) in guard let self = self else { return } // 只有当前cell的标记和请求的indexPath一致时,才更新图片 if cell.tag == indexPath.row { DispatchQueue.main.async { cell.gelleryImages.image = image ?? UIImage(named: "placeholder") } } } return cell }
三、Facebook图片加载的通用优化思路
你的Facebook图片加载应该也是犯了同样的错误:一次性发起所有下载请求,等全部完成再刷新列表。优化逻辑和相册一致:
- 先获取所有图片的URL列表,保存到数组,立即刷新CollectionView显示占位图。
- 在
cellForItemAt中,针对当前cell对应的URL发起异步下载请求。 - 处理cell复用问题(比如用tag或绑定URL),避免图片错乱。
- 推荐使用成熟的图片缓存库(比如Kingfisher、SDWebImage),它们已经封装了异步加载、缓存、复用处理的逻辑,一行代码就能搞定:
// 假设你有一个保存Facebook图片URL的数组:facebookImageURLs func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "galleryCell", for: indexPath) as! GalleryCollectionViewCell let url = facebookImageURLs[indexPath.row] // 用Kingfisher异步加载+缓存图片,自动处理复用 cell.gelleryImages.kf.setImage(with: url, placeholder: UIImage(named: "placeholder")) return cell }
四、核心优化总结
- 永远不要同步请求大量资源,必须用异步操作避免阻塞主线程。
- 拒绝预加载所有图片到内存,按需加载能大幅降低初始等待时间和内存占用。
- 处理好CollectionView的cell复用问题,避免滚动时出现图片错乱。
- 网络图片优先使用成熟的缓存库,节省开发时间的同时提升稳定性。
内容的提问来源于stack exchange,提问作者Matt199
相关产品推荐
相关产品推荐

