求求你试试 DiffableDataSource!别再手算 indexPath 了(否则迟早崩)
你是不是也遇到过这些问题:
insertRows一插就崩:Invalid update: invalid number of rows- 多 section 一更新就乱:indexPath 不匹配
- 想做动画很麻烦:
beginUpdates+performBatchUpdates - 更新某一条会闪烁:
reloadData() - 复杂场景(聊天流、瀑布流、Feed 流)代码写到怀疑人生
这些问题的本质是:
你在手动维护 UI 和数据的同步,而 TableView/CollectionView 的 index 一旦不一致,就会瞬间把你崩回桌面。
但自从 iOS 13 开始,Apple 已经给了我们一个"几乎不会崩"的方案:
DiffableDataSource。
为什么要用 DiffableDataSource?
一句话:
你只管"数据最终长什么样",UI 自动算出该怎么更新。
它的三大优势:
- 不再维护 indexPath
Diffable 不依赖 index,所有操作基于 item 唯一标识(Hashable),避免大部分 crash。 - 动画自动处理
插入、删除、移动、局部更新都自动生成动画,不再写batchUpdates。 - 复杂列表场景刚需
多 section、聊天流、Feed、搜索、瀑布流......传统方式写起来代码膨胀,Diffable 轻松搞定。
传统 DataSource 容易崩的案例
假设你有一个 users: [User] 的数据源,传统做法:
less
users.insert(user, at: index)
tableView.insertRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
问题:
- 异步修改数据时,index 一不对齐就崩
- 多次 insert/delete 后,indexPath 不匹配
- batchUpdates 太复杂,容易出错
经典报错:
Invalid update: invalid number of rows in section 0
DiffableDataSource 的安全写法
核心思想:操作 item 标识符,系统自动计算差异并更新 UI。
数据模型
csharp
struct User: Hashable {
let id = UUID()
var name: String
}
Section
arduino
enum Section {
case main
}
DataSource 初始化
swift
class ViewController: UIViewController {
var tableView: UITableView!
var dataSource: UITableViewDiffableDataSource<section>!
var users: [User] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView = UITableView(frame: view.bounds, style: .plain)
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
view.addSubview(tableView)
dataSource = UITableViewDiffableDataSource<section>(tableView: tableView) { tableView, indexPath, user in
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = user.name
return cell
}
applySnapshot(animated: false)
}
}
Snapshot 封装
swift
func applySnapshot(animated: Bool = true) {
var snapshot = NSDiffableDataSourceSnapshot<section>()
snapshot.appendSections([.main])
snapshot.appendItems(users)
dataSource.apply(snapshot, animatingDifferences: animated)
}
Diffable 常用操作示例
1. 插入某下标
swift
func insertUser(_ user: User, atIndex index: Int) {
var snapshot = dataSource.snapshot()
var items = snapshot.itemIdentifiers(inSection: .main)
let safeIndex = max(0, min(index, items.count))
if safeIndex == items.count {
snapshot.appendItems([user], toSection: .main)
} else {
let before = items[safeIndex]
snapshot.insertItems([user], beforeItem: before)
}
users.insert(user, at: safeIndex)
dataSource.apply(snapshot, animatingDifferences: true)
}
2. 删除某下标
swift
func deleteUser(atIndex index: Int) {
var snapshot = dataSource.snapshot()
let items = snapshot.itemIdentifiers(inSection: .main)
guard items.indices.contains(index) else { return }
let itemToDelete = items[index]
snapshot.deleteItems([itemToDelete])
users.remove(at: index)
dataSource.apply(snapshot, animatingDifferences: true)
}
3. 移动 item
css
func moveItem(from fromIndex: Int, to toIndex: Int) {
var snapshot = dataSource.snapshot()
var items = snapshot.itemIdentifiers(inSection: .main)
guard items.indices.contains(fromIndex),
items.indices.contains(toIndex) else { return }
let item = items.remove(at: fromIndex)
items.insert(item, at: toIndex)
snapshot.deleteSections([.main])
snapshot.appendSections([.main])
snapshot.appendItems(items, toSection: .main)
let moved = users.remove(at: fromIndex)
users.insert(moved, at: toIndex)
dataSource.apply(snapshot, animatingDifferences: true)
}
4. 更新 item 的字段(安全写法)
scss
func updateUserName(atIndex index: Int, newName: String) {
var snapshot = dataSource.snapshot()
let items = snapshot.itemIdentifiers(inSection: .main)
guard items.indices.contains(index) else { return }
let oldItem = items[index]
let updatedItem = User(id: oldItem.id, name: newName)
// 更新本地 users 数组
users[index] = updatedItem
// 判断是否有下一个 item 可作为插入参照
if index + 1 < items.count {
let nextItem = items[index + 1]
snapshot.deleteItems([oldItem])
snapshot.insertItems([updatedItem], beforeItem: nextItem)
} else {
// 如果是最后一个,直接删除再 append
snapshot.deleteItems([oldItem])
snapshot.appendItems([updatedItem], toSection: .main)
}
dataSource.apply(snapshot, animatingDifferences: true)
}
✅ 不依赖自定义 safe 下标
✅ 自动处理最后一个 item
✅ 保持 id 不变,动画安全
总结
DiffableDataSource 的核心优势:
- 不再维护 indexPath → 避免崩溃
- 动画自动生成 → 插入/删除/移动/更新一气呵成
- 复杂场景稳如老狗 → 多 section、Feed、聊天、搜索、瀑布流都轻松
一句话:Diffable 是 2025 年 iOS 列表开发的标配。
不用它,你会花大量时间调 index;
用了它,你会怀疑自己以前为什么受苦。