You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift中Collection View视频Cell复用引发CPU占用过高问题求助

CollectionView视频列表CPU占用过高问题及解决方案

我在Swift的CollectionView Cell中实现了视频播放功能,已经做到同一时间只播放一个视频,但滚动列表时CPU占用率飙升到200%以上,严重影响设备性能。另外,原本打算把播放暂停逻辑绑定到Cell的XIB按钮上,但不知道怎么正确处理,现在需要解决CPU高负载的问题。

当前代码

VideoCollectionViewCell代码

import UIKit
import AVFoundation

class VideoCollectionViewCell: UICollectionViewCell {
    var player: AVPlayer?
    var playerLayer: AVPlayerLayer?
    var playPauseButton: UIButton!
    override func awakeFromNib() {
        super.awakeFromNib()
        // Setup player layer
        playerLayer = AVPlayerLayer()
        playerLayer?.videoGravity = .resizeAspect
        contentView.layer.addSublayer(playerLayer!)
        // Setup play/pause button
        playPauseButton = UIButton(type: .system)
        playPauseButton.setTitle("Play", for: .normal)
        playPauseButton.addTarget(self, action: #selector(playPauseTapped), for: .touchUpInside)
        contentView.addSubview(playPauseButton)
        // Layout button
        playPauseButton.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            playPauseButton.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
            playPauseButton.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
            playPauseButton.widthAnchor.constraint(equalToConstant: 100),
            playPauseButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    override func layoutSubviews() {
        super.layoutSubviews()
        playerLayer?.frame = contentView.bounds
    }
    func configure(with url: URL) {
        player = AVPlayer(url: url)
        playerLayer?.player = player
    }
    @objc func playPauseTapped() {
        if player?.timeControlStatus == .playing {
            pause()
        } else {
            play()
        }
    }
    func play() {
        player?.play()
        playPauseButton.setTitle("Pause", for: .normal)
    }
    func pause() {
        player?.pause()
        playPauseButton.setTitle("Play", for: .normal)
    }
    override func prepareForReuse() {
        super.prepareForReuse()
        player?.pause()
        playerLayer?.player = nil
    }
}

ViewController代码

import UIKit
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
    @IBOutlet weak var collectionView: UICollectionView!
    var videoURLs: [URL] = [/* Your video URLs here */]
    var currentlyPlayingIndex: IndexPath?
    override func viewDidLoad() {
        super.viewDidLoad()
        collectionView.delegate = self
        collectionView.dataSource = self
        collectionView.register(UINib(nibName: "VideoCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "VideoCell")
    }
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return videoURLs.count
    }
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "VideoCell", for: indexPath) as! VideoCollectionViewCell
        cell.configure(with: videoURLs[indexPath.item])
        if indexPath == currentlyPlayingIndex {
            cell.play()
        } else {
            cell.pause()
        }
        return cell
    }
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        if let currentIndex = currentlyPlayingIndex, let currentCell = collectionView.cellForItem(at: currentIndex) as? VideoCollectionViewCell {
            currentCell.pause()
        }
        let cell = collectionView.cellForItem(at: indexPath) as! VideoCollectionViewCell
        cell.play()
        currentlyPlayingIndex = indexPath
    }
    // Ensuring that the video continues playing even when the cell goes off-screen
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        guard let currentlyPlayingIndex = currentlyPlayingIndex else { return }
        if let cell = collectionView.cellForItem(at: currentlyPlayingIndex) as? VideoCollectionViewCell {
            // Ensure the cell keeps playing the video if it's the currently playing index
            cell.play()
        }
    }
}

优化方案

1. 彻底清理复用Cell的播放器资源

当前prepareForReuse只暂停并清空playerLayer的player,没有彻底销毁播放器实例,导致资源残留。修改如下:

override func prepareForReuse() {
    super.prepareForReuse()
    pause()
    // 清空播放项,释放资源
    player?.replaceCurrentItem(with: nil)
    player = nil
    // 移除playerLayer,避免图层残留
    playerLayer?.removeFromSuperlayer()
    playerLayer = nil
    // 重置按钮状态
    playPauseButton.setTitle("Play", for: .normal)
}

2. 修复滚动时的无效播放触发

原scrollViewDidScroll每次滚动都调用play(),导致CPU频繁执行播放逻辑。改为判断播放Cell是否在可视区域,不在则暂停并清空当前播放索引:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    guard let currentIndex = currentlyPlayingIndex else { return }
    // 获取可视区域范围
    let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.bounds.size)
    // 获取当前播放Cell的frame
    guard let cellFrame = collectionView.layoutAttributesForItem(at: currentIndex)?.frame else {
        currentlyPlayingIndex = nil
        return
    }
    // 如果Cell不在可视区域,暂停播放
    if !visibleRect.intersects(cellFrame) {
        if let cell = collectionView.cellForItem(at: currentIndex) as? VideoCollectionViewCell {
            cell.pause()
        }
        currentlyPlayingIndex = nil
    }
}

3. 正确绑定XIB中的播放按钮

如果按钮在XIB中,直接通过IBOutlet关联,无需代码创建:

  1. 在XIB中给按钮添加IBOutlet到Cell类:
class VideoCollectionViewCell: UICollectionViewCell {
    @IBOutlet weak var playPauseButton: UIButton!
    var player: AVPlayer?
    var playerLayer: AVPlayerLayer?
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // 绑定按钮点击事件
        playPauseButton.addTarget(self, action: #selector(playPauseTapped), for: .touchUpInside)
        // 初始化playerLayer并放在按钮下方
        playerLayer = AVPlayerLayer()
        playerLayer?.videoGravity = .resizeAspect
        contentView.layer.insertSublayer(playerLayer!, below: playPauseButton.layer)
    }
    
    // 其他方法保持不变...
}
  1. 在XIB中设置按钮的约束和样式,无需代码创建布局。

4. 减少cellForItemAt中的无效操作

原代码每次复用Cell都会调用play()或pause(),优化为仅当Cell是当前播放项且在可视区域时才播放:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "VideoCell", for: indexPath) as! VideoCollectionViewCell
    cell.configure(with: videoURLs[indexPath.item])
    
    let isCurrentPlaying = indexPath == currentlyPlayingIndex
    let isVisible = collectionView.indexPathsForVisibleItems.contains(indexPath)
    
    if isCurrentPlaying && isVisible {
        cell.play()
    } else {
        cell.pause()
    }
    return cell
}

5. 优化AVPlayer初始化与硬件解码

使用AVAsset和AVPlayerItem初始化播放器,启用硬件加速并调整缓冲时长,减少CPU和内存占用:

func configure(with url: URL) {
    let asset = AVAsset(url: url)
    let playerItem = AVPlayerItem(asset: asset)
    // 缩短缓冲时长,减少内存占用
    playerItem.preferredForwardBufferDuration = 1.0
    player = AVPlayer(playerItem: playerItem)
    playerLayer?.player = player
}

内容的提问来源于stack exchange,提问作者Ajay Chudasama

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 05:25:59