性能
列表卡,多半不是 cell 复用没写
复用标识稳定之后,时间通常花在高度、富文本和没有取消的图片任务上。
UITableView / UICollectionView 的复用机制很老了,多数项目也写了 reuseIdentifier。滚动仍然掉帧,常见是 cell 里做了重复工作:每次 layout 都算一遍富文本、Auto Layout 约束互相打架、图片请求不取消、或者一次 snapshot 把几千行在主线程上建出来。
高度
自动高度方便,但每一行第一次出现都要做一次完整布局。行高只跟标题和宽度有关时,按宽度缓存高度。宽度变了(旋转、分屏)再废掉缓存。estimatedHeight 可以给一个接近的值,差一个数量级时,快速滚动的滚动条会跳。
复杂 cell 把“纯展示模型”提前算好:字符串、是否显示角标、缩略图尺寸。cellForItem 里只赋值。不要在 cell 里访问单例去格式化日期。
复用和取消
final class PhotoCell: UICollectionViewCell {
private var loadTask: Task<Void, Never>?
private let imageView = UIImageView()
override func prepareForReuse() {
super.prepareForReuse()
loadTask?.cancel()
loadTask = nil
imageView.image = nil
}
func setImageURL(_ url: URL) {
loadTask = Task {
let image = await ImageStore.shared.thumbnail(for: url, maxPixel: 360)
if Task.isCancelled { return }
imageView.image = image
}
}
}
prefetch 用来提前解码即将进入屏幕的图,不是用来发一堆不会取消的请求。滑得很快时,prefetch 的索引和真正显示的索引会差一截,取消逻辑必须在。
Diffable data source
NSDiffableDataSourceSnapshot 解决的是“怎么把差异应用到界面”,不是“差异可以在主线程上随便算”。上万条的 snapshot 构造、item identifier 的哈希,都要看是不是堵在主线程。标识不稳定(每次刷新都是新的 UUID)会让列表整表刷新,复用等于没有。
一个实用的顺序:先用 Hitches 确认时间在 cell 的 layout 还是在你绑定数据的函数里。在 layout 里,就减约束、缓存高度。在绑定里,就把计算挪出去,并加上取消。