跳到主要内容

UITableView 与 UICollectionView

问题

UITableView 的 Cell 复用原理是什么?如何优化列表性能?DiffableDataSource 解决了什么问题?

答案

Cell 复用原理

// 注册(viewDidLoad 中)
tableView.register(MyCell.self, forCellReuseIdentifier: "MyCell")

// 复用
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath) as! MyCell
cell.configure(with: data[indexPath.row])
return cell
}
复用导致的问题

Cell 被复用时,上一次的状态(图片、选中状态等)可能残留。必须在 cellForRowAtprepareForReuse() 中重置所有 UI 状态

性能优化

优化手段说明
高度缓存预计算并缓存 Cell 高度,避免重复计算
预加载UITableViewDataSourcePrefetching 提前加载数据
减少层级减少 Cell 中的视图层级和透明度
异步绘制复杂内容在后台线程绘制为 Bitmap
避免离屏渲染不要同时设 cornerRadius + masksToBounds
图片异步加载滑动时暂停加载,停下时恢复
// 高度缓存
var heightCache: [IndexPath: CGFloat] = [:]

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return heightCache[indexPath] ?? 60
}

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
heightCache[indexPath] = cell.frame.height
}

// Prefetching
extension VC: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
for indexPath in indexPaths {
loadImage(for: data[indexPath.row])
}
}
}

DiffableDataSource

传统 reloadData 的问题:无动画、全量刷新。DiffableDataSource 基于数据快照自动计算差异并执行动画更新:

// 1. 定义 Section 和 Item
enum Section { case main }
struct Item: Hashable {
let id: UUID
let title: String
}

// 2. 创建 DataSource
var dataSource: UITableViewDiffableDataSource<Section, Item>!

dataSource = UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item in
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = item.title
return cell
}

// 3. 应用快照更新
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.main])
snapshot.appendItems(items, toSection: .main)
dataSource.apply(snapshot, animatingDifferences: true)

UICollectionView Compositional Layout

iOS 13+ 的新布局 API,取代传统 UICollectionViewFlowLayout:

// Section → Group → Item 三层结构
let layout = UICollectionViewCompositionalLayout { sectionIndex, env in
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5),
heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: 4, leading: 4, bottom: 4, trailing: 4)

let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .absolute(200))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])

let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .continuous // 横向滚动
return section
}

常见面试问题

Q1: dequeueReusableCell(withIdentifier:for:) 和不带 for: 的区别?

答案

  • for::如果没有可复用的 Cell,系统自动创建新的(需要提前 register)
  • 不带 for::返回 nil 时需要手动创建

推荐用带 for: 的版本,配合 register 使用。

Q2: UITableView 滑动卡顿怎么排查和优化?

答案

  1. Instruments - Time Profiler:找到 cellForRowAt 中的耗时操作
  2. 避免主线程阻塞:图片解码、数据处理放后台线程
  3. 高度缓存:避免 AutoLayout 重复计算
  4. 减少透明和离屏渲染:用 Color Blended Layers 检查
  5. 图片降采样:大图展示前先等比缩小到实际显示尺寸

Q3: DiffableDataSource 相比传统 DataSource 的优势?

答案

  • 自动计算数据差异,无需手动 insertRows / deleteRows
  • 避免 "number of rows mismatch" 崩溃
  • 内置动画
  • 线程安全(可以在后台线程构建 snapshot)
  • Item 需要 Hashable,强制数据唯一性

相关链接