CollectionView滚动时App卡顿,离线图片缓存及广告加载问题求助
看起来你的CollectionView卡顿问题主要来自图片加载和广告渲染的主线程阻塞,结合Kingfisher的最佳实践和广告加载优化,给你整理了一套可行的解决方案:
一、Kingfisher 图片加载优化(核心解决卡顿)
你现在用的KingfisherManager.shared.retrieveImage其实不是最优的方式,Kingfisher本身提供了针对UICollectionViewCell的扩展方法,能自动处理cell复用、异步加载和缓存管理,比手动调用更高效,还能避免复用导致的图片错乱问题。
1. 改用Kingfisher的Cell扩展方法
直接在cell的imageView上调用kf.setImage,它会自动处理缓存、异步加载,还支持占位图和加载状态,离线时会自动读取本地缓存:
// 在cellForItemAt方法中配置cell时 guard let imageURL = URL(string: imageUrl) else { cell.imageView.image = UIImage(named: "invalid_url_placeholder") return cell } cell.imageView.kf.setImage( with: imageURL, placeholder: UIImage(named: "loading_placeholder"), // 替代活动加载器的占位图 options: [ .cacheOriginalImage, // 强制缓存原图,确保离线能读取 .backgroundDecode, // 后台解码图片,避免主线程卡顿 .transition(.fade(0.2)) // 可选:淡入过渡提升视觉体验 ] )
2. 修复cell复用的加载状态问题
如果之前用了活动加载器,一定要在cell复用的时候重置状态,避免滚动时加载器乱跳或者旧下载任务干扰新cell:
class YourCollectionViewCell: UICollectionViewCell { @IBOutlet weak var activityIndicator: UIActivityIndicatorView! @IBOutlet weak var imageView: UIImageView! override func prepareForReuse() { super.prepareForReuse() activityIndicator.stopAnimating() imageView.kf.cancelDownloadTask() // 取消未完成的下载任务 imageView.image = nil // 重置图片,避免复用残留 } }
如果需要保留活动加载器,可以在kf.setImage的回调里控制它的启停:
cell.activityIndicator.startAnimating() cell.imageView.kf.setImage( with: imageURL, options: [.cacheOriginalImage, .backgroundDecode], completionHandler: { result in cell.activityIndicator.stopAnimating() // 处理加载失败(比如离线且无缓存的情况) if case .failure(_) = result { cell.imageView.image = UIImage(named: "offline_placeholder") } } )
二、FlurryAds 加载优化(减少滚动干扰)
广告加载和渲染是另一个常见的卡顿诱因,要尽量避免在滚动过程中执行广告相关操作:
1. 滚动时暂停广告请求
在CollectionView滚动时暂停广告加载,停止滚动后再加载可见区域的广告:
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { // 暂停Flurry广告请求,避免滚动时后台加载占用资源 FlurryAds.pauseAdRequests() } func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { // 恢复广告请求,加载当前可见的广告cell FlurryAds.resumeAdRequests() loadVisibleAds() } private func loadVisibleAds() { let visibleIndexPaths = collectionView.indexPathsForVisibleItems for indexPath in visibleIndexPaths { // 假设你的广告有固定的位置标识,比如数据源里的isAd字段 if yourDataSource[indexPath.item].isAd { guard let adCell = collectionView.cellForItem(at: indexPath) as? AdCollectionViewCell else { continue } adCell.loadFlurryAd() } } }
2. 广告cell的复用清理
广告cell在复用前要彻底清理旧广告视图,避免内存泄漏和渲染冲突:
class AdCollectionViewCell: UICollectionViewCell { @IBOutlet weak var adContainerView: UIView! override func prepareForReuse() { super.prepareForReuse() // 移除所有旧广告视图 adContainerView.subviews.forEach { $0.removeFromSuperview() } } func loadFlurryAd() { // 后台初始化广告,主线程添加视图 DispatchQueue.global().async { let flurryAd = FlurryAd(placementId: "your_ad_placement_id") // 配置广告参数... DispatchQueue.main.async { self.adContainerView.addSubview(flurryAd.view) flurryAd.view.frame = self.adContainerView.bounds flurryAd.load() } } } }
三、额外的CollectionView性能优化
- 简化cell视图结构:尽量减少cell的子视图数量,避免复杂的AutoLayout约束,降低布局计算的主线程开销。
- 开启预加载:利用UICollectionView的预加载功能,提前加载即将显示的图片,减少滚动时的加载延迟:
extension YourViewController: UICollectionViewDataSourcePrefetching { func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) { for indexPath in indexPaths { let imageUrl = yourDataSource[indexPath.item].imageUrl if let url = URL(string: imageUrl) { // 预加载图片到Kingfisher缓存 KingfisherManager.shared.retrieveImage(with: url, options: [.cacheOriginalImage]) } } } }
记得在ViewController里开启预加载:collectionView.prefetchDataSource = self
四、离线图片验证
Kingfisher的缓存是持久化的,只要你配置了.cacheOriginalImage,离线时会自动读取本地缓存。如果需要手动判断缓存是否存在,可以用这个方法:
let imageURL = URL(string: imageUrl)! if ImageCache.default.imageCachedType(forKey: imageURL.absoluteString).cached { // 缓存存在,直接加载 cell.imageView.kf.setImage(with: imageURL) } else { // 离线且无缓存,显示离线占位图 cell.imageView.image = UIImage(named: "offline_placeholder") }
不过一般不需要手动判断,Kingfisher会自动处理离线场景,加载失败时会进入completion的failure分支。
内容的提问来源于stack exchange,提问作者Ram Mohan

