1. 先理解痛点:传统 UITableViewDataSource 到底哪里别扭
1.1 reloadData 的粗暴哲学
在 UITableViewDiffableDataSource 出现之前,iOS 开发者跟列表打交道的方式几乎是一个模子刻出来的:实现 UITableViewDataSource 协议,返回 numberOfSections、numberOfRowsInSection、cellForRowAt,然后在数据发生变化时调用 reloadData() 或 reloadRows(at:with:)。这种方式在数据量小、页面逻辑简单的项目里跑得很顺,但一旦列表开始复杂,问题就扑面而来。
我印象最深的是那个经典场景:后台推送了一条新消息,你需要把新数据插到列表顶部。传统写法是先把数据源数组 update 一下,然后 reloadData()。表面上没问题,但用户端看到的效果是——整个列表闪一下、滚动位置被重置、正在播放的视频突然断掉、图片因为 cell 被复用重新加载了一遍。这是因为 reloadData() 压根不关心你改了什么,它把“整张表重画一次”当成唯一策略。数据量小时还能忍,几百行以上、带图片、带视频、带轮播的页面,这种全量刷新会直接把流畅度打没。
更隐蔽的是数据源不同步导致的崩溃。传统写法里,你告诉 UITableView 某个 section 有 5 行,但实际返回的 cellForRowAt 却因为数组越界等其他原因拿不到数据,应用直接闪退。这种崩溃在团队协作项目里非常常见,尤其是多人维护同一个列表页、各自改动数据逻辑时,数据源数组和 UI 的表现经常不在一个节奏上。
1.2 手动 diff 的人为失误
有人会说,那不用 reloadData,用 reloadRows 不就行了?问题是 reloadRows 要求你自己计算出“哪些行需要刷新、哪些行需要删除、哪些行需要插入”,这个计算逻辑叫 diff。自己写 diff 算法,小列表还能应付,一旦涉及多 Section、动态增删、拖拽排序、搜索过滤,几乎每个版本迭代都会冒出新 bug。要么漏掉某个 case 导致越界崩溃,要么动画错乱,要么在某些边界情况下数据源不同步,然后用户一脸茫然地看着列表里多了一行幽灵数据。
就算你用了第三方 diff 库,比如 IGListKit 那套方案,也得先调整数据模型、学习一套新的列表架构,迁移成本不低。而且这些库的定位更偏向 Feed 流这种超复杂列表,对大多数业务页面来说是杀鸡用了牛刀。
1.3 DiffableDataSource 的设计思路
2019 年,苹果在 iOS 13 里推出了 UITableViewDiffableDataSource 和 UICollectionViewDiffableDataSource。它的核心变化是:开发者不再手动管理数据源数组和表格的对应关系,而是把数据装进一个叫 Snapshot 的模型里,告诉 DiffableDataSource“这是当前完整的数据状态”,剩下的比较、动画、局部刷新全部由系统自动完成。
这个设计其实很像 Git 的工作方式。你不需要告诉 Git 具体删了哪一行、改了哪一行,只需要提交一个完整的新版本,Git 自己 diff 出变化。DiffableDataSource 也是一样,每次把新的 Snapshot apply 上去,系统会对比新旧快照的差异,自动决定插入、删除、移动哪些 cell,并且自带优雅的动画效果。
这里有个关键前提:列表里的每一条数据模型必须遵守 Hashable 协议。因为系统要靠 Hashable 的哈希值来识别“这一行还是原来的数据还是新来的数据”。理解了这一点,后续很多坑你就能提前避开,比如同一个模型里某个字段一变,整个 cell 就被判定为新行、导致动画异常,其实就是 Hashable 的实现不严谨。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 快速上手:第一次写出 DiffableDataSource 列表
2.1 搭建最小可用列表
先看一个最简单的例子。假设我们要做一个联系人列表,每个 section 代表姓氏首字母,cell 显示联系人姓名。
依赖注入的阶段,我们需要定义两种类型:
Section:枚举,遵守 Hashable,可以用首字母作为原始值。Contact:结构体,遵守 Hashable,包含姓名、头像 URL 等字段。
数据源属性可以这么声明:
swift复制private var dataSource: UITableViewDiffableDataSource<Section, Contact>!
接下来在 viewDidLoad 里创建数据源,并实现 cellProvider 闭包:
swift复制dataSource = UITableViewDiffableDataSource<Sectio
