卡顿-TableView优化
列表滚动是用户最敏感的交互场景之一。本文介绍UITableView和UICollectionView的性能优化方案。 列表卡顿的常见原因 flowchart TB subgraph frame["每一帧(16.67ms内)需要完成"] A["1. 计算可见Cell"] --> B["2. 复用或创建Cell"] B --> C["3. 配置Cell内容"] C --> C1["设置文本"] C --> C2["加载图片"] C --> C3["计算布局"] C --> C4["渲染视图"] C1 --> D["4. 提交渲染"] C2 --> D C3 --> D C4 --> D end D --> E["⚠️ 任何一步超时都会导致掉帧"] 常见问题 问题 原因 影响 高度计算慢 复杂布局、Auto Layout 滚动卡顿 Cell配置慢 大量视图操作、图片加载 滚动卡顿 复用失效 未正确注册、identifier错误 内存暴涨、卡顿 离屏渲染 圆角、阴影 GPU瓶颈 图片加载 主线程解码 滚动卡顿 Cell复用机制 正确使用复用 class OptimizedTableViewController: UITableViewController { private let cellIdentifier = "OptimizedCell" override func viewDidLoad() { super.viewDidLoad() // 注册Cell类(推荐) tableView.register(OptimizedCell.self, forCellReuseIdentifier: cellIdentifier) // 或注册Nib // tableView.register(UINib(nibName: "OptimizedCell", bundle: nil), forCellReuseIdentifier: cellIdentifier) } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { // 使用dequeueReusableCell(带indexPath的版本会自动创建) let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! OptimizedCell // 配置Cell let item = items[indexPath.row] cell.configure(with: item) return cell } } Cell的prepareForReuse class OptimizedCell: UITableViewCell { private let titleLabel = UILabel() private let avatarImageView = OptimizedImageView() private var currentTask: URLSessionTask? override func prepareForReuse() { super.prepareForReuse() // 重置状态 titleLabel.text = nil avatarImageView.image = nil // 取消进行中的任务 currentTask?.cancel() currentTask = nil avatarImageView.cancelLoading() } func configure(with item: Item) { titleLabel.text = item.title // 异步加载图片 avatarImageView.setImage(from: item.avatarURL, placeholder: UIImage(named: "placeholder")) } } 高度缓存 问题:每次都计算高度 // 问题代码:每次滚动都重新计算 func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let item = items[indexPath.row] return calculateHeight(for: item) // 耗时操作 } 解决方案1:预计算并缓存 class HeightCachedTableViewController: UITableViewController { private var items: [Item] = [] private var heightCache: [IndexPath: CGFloat] = [:] func setItems(_ newItems: [Item]) { items = newItems heightCache.removeAll() // 预计算高度(可以在后台线程) precomputeHeights() tableView.reloadData() } private func precomputeHeights() { let width = tableView.bounds.width for (index, item) in items.enumerated() { let indexPath = IndexPath(row: index, section: 0) heightCache[indexPath] = calculateHeight(for: item, width: width) } } private func calculateHeight(for item: Item, width: CGFloat) -> CGFloat { // 计算文本高度 let textHeight = item.content.boundingRect( with: CGSize(width: width - 32, height: .greatestFiniteMagnitude), options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: [.font: UIFont.systemFont(ofSize: 16)], context: nil ).height return ceil(textHeight) + 60 // 加上其他元素的高度 } override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return heightCache[indexPath] ?? UITableView.automaticDimension } // 数据更新时更新缓存 func updateItem(at indexPath: IndexPath, with item: Item) { items[indexPath.row] = item heightCache[indexPath] = calculateHeight(for: item, width: tableView.bounds.width) tableView.reloadRows(at: [indexPath], with: .automatic) } } 解决方案2:使用estimatedHeight class EstimatedHeightTableViewController: UITableViewController { private var heightCache: [IndexPath: CGFloat] = [:] override func viewDidLoad() { super.viewDidLoad() // 设置估算高度(重要!) tableView.estimatedRowHeight = 100 tableView.rowHeight = UITableView.automaticDimension } override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { // 缓存实际显示的高度 heightCache[indexPath] = cell.bounds.height } override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { // 优先返回缓存的高度 return heightCache[indexPath] ?? 100 } } 解决方案3:固定高度 // 如果所有Cell高度相同,直接设置固定值 override func viewDidLoad() { super.viewDidLoad() tableView.rowHeight = 80 // 固定高度,性能最好 tableView.estimatedRowHeight = 0 // 关闭估算 } 异步渲染 基本原理 将复杂的渲染工作移到后台线程: ...