Swift实现UICollectionView视频预加载缓存 优化点击播放体验
iOS UICollectionView 视频预加载优化方案
现有点击跳转后才加载视频的逻辑,本质是资源加载时机太晚,把加载时机提前到列表滑动阶段、配合缓存复用就能解决点击后等待过长的问题,具体实现步骤如下:
- 搭建轻量视频资源缓存池
用系统自带的NSCache做内存级缓存即可,key用视频URL的字符串标识,value存储预加载完成的AVURLAsset实例,不要缓存AVPlayer这类和页面layer绑定的重资源。NSCache会在系统内存不足时自动释放闲置缓存,不需要额外写太多内存监听的清理逻辑。可以把缓存逻辑抽成单例管理者,方便列表页和播放页共同访问。 - 利用系统预加载协议触发提前加载
不需要自己计算滑动速度、预判滑动方向,直接用UICollectionView自带的UICollectionViewDataSourcePrefetching协议,系统会自动识别用户接下来大概率会浏览到的cell位置,在回调里触发对应视频资源的预加载:
记得给列表开启预加载能力:// 列表页实现预加载代理 extension MomentListViewController: UICollectionViewDataSourcePrefetching { func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) { for indexPath in indexPaths { // 越界校验 guard indexPath.item < dataSource.count, let moment = dataSource[indexPath.item] as? Moment, let urlStr = moment.videoUrl, let videoUrl = URL(string: urlStr) else { continue } // 已经缓存过的资源跳过,不重复加载 if VideoCacheManager.shared.containAsset(for: urlStr) { continue } // 初始化资源,只加载播放必需的关键属性 let asset = AVURLAsset(url: videoUrl) asset.loadValuesAsynchronously(forKeys: ["playable", "tracks"]) { var keyLoadError: NSError? let loadStatus = asset.statusOfValue(forKey: "playable", error: &keyLoadError) // 加载成功才存入缓存 if loadStatus == .loaded, keyLoadError == nil { VideoCacheManager.shared.storeAsset(asset, for: urlStr) } } } } // 滑出预加载范围的任务直接取消,节省带宽 func collectionView(_ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath]) { for indexPath in indexPaths { guard indexPath.item < dataSource.count, let moment = dataSource[indexPath.item] as? Moment, let urlStr = moment.videoUrl else { continue } VideoCacheManager.shared.cancelLoad(for: urlStr) } } }collectionView.prefetchingEnabled = true,同时设置collectionView.prefetchDataSource = self。 - 改造播放页初始化逻辑,优先命中缓存
跳转播放页时把对应视频的URL参数传入,初始化播放器时优先从缓存池取预加载完成的资源生成播放项,没有命中缓存再走原始的URL加载逻辑做兜底。改造后的configureVideo方法参考:fileprivate func configureVideo(with moment : Moment){ guard let urlString = moment.videoUrl else { return } guard let videoUrl = URL(string: urlString) else { return } let playerItem: AVPlayerItem // 优先使用预加载完成的缓存资源 if let cachedAsset = VideoCacheManager.shared.getAsset(for: urlString) { playerItem = AVPlayerItem(asset: cachedAsset) } else { // 缓存未命中走原始加载逻辑兜底 playerItem = AVPlayerItem(url: videoUrl) } self.player = AVPlayer(playerItem: playerItem) // 原有的KVO、通知、图层配置逻辑保留 self.player!.addObserver(self, forKeyPath: #keyPath(player.currentItem.isPlaybackLikelyToKeepUp), options: .new, context: &playbackLikelyToKeepUpContext) NotificationCenter.default.addObserver(self, selector: #selector(videoReachedEnd), name: .AVPlayerItemDidPlayToEndTime, object: self.player?.currentItem) self.playerLayer = AVPlayerLayer(player: player) self.view.layer.insertSublayer(playerLayer!, at: 0) self.playerLayer?.frame = self.view.bounds self.player?.play() } - 补充必要的优化逻辑
- 可以根据网络状态调整预加载策略:WiFi环境下按系统返回的预加载范围全量加载,蜂窝网络下只预加载当前可见cell前后1-2条的视频,避免消耗过多用户流量。
- 原播放页代码缺少观察者移除逻辑,记得在
deinit方法中移除KVO和对应通知,避免内存泄漏。 - 如果需要实现二次启动不重复加载的持久化缓存,可以通过
AVAssetResourceLoader自定义资源加载代理,把下载完成的视频数据存入沙盒,后续相同URL直接读本地文件即可,内存级预加载已经能解决点击跳转的等待问题,持久化缓存可以后续迭代再加。
内容的提问来源于stack exchange,提问作者StackGU
相关产品推荐
相关产品推荐

