UITableViewDiffableDataSource在iPad调用apply时报itemIdentifier无效闪退如何解决
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: itemIdentifier'
问题根因
- 模型Hashable实现不符合Diffable数据源要求
你当前使用Swift自动合成的Hashable实现,存在两个致命问题:Section结构体把items数组作为Hashable的计算因子:同一个kind的分组只要内容变化,就会被Diffable数据源判定为完全不同的新分组,而不是同一个分组更新内容,旧分组删除和新分组插入的时序如果和TableView的布局刷新重叠就会触发崩溃。Item结构体包含闭包action和UIImage类型属性:闭包不会参与Hashable计算,UIImage的Hash值计算依赖底层图像数据和系统实现,很容易出现两个业务上不同的Item判定为同一个、或者同一个Item多次生成Hash值不一致的情况,导致Diff算法计算出来的变更路径非法,触发itemIdentifier不满足的断言。
- iPad独有场景触发崩溃
iPad设备上更易触发该问题的原因:- 分屏、悬浮窗、横竖屏切换的概率远高于iPhone,会频繁触发TableView的重新布局
- 若你的控制器嵌套在
UISplitViewController中,页面加载过程中会多次触发布局回调,很容易出现两次apply快照的操作重叠,或者快照生成时TableView正处于更新中的状态
- 代码逻辑笔误
你提供的示例代码中configFirstSection返回值是[Item],但调用处直接把它当成Section加入分组数组,会导致生成的快照结构本身就不符合预期,也可能触发崩溃。
修复方案
- 重写Section和Item的Hashable实现,仅用业务唯一ID计算Hash
给Section和Item加唯一标识符,Hash和Equatable判定仅用ID,避免无关属性干扰Diff计算:
struct Section: Hashable { enum Kind: Hashable, CaseIterable { case `default` case full } // 直接用kind作为分组唯一标识即可 let kind: Kind let title: String? = nil let items: [Item] // 仅用kind判定相等和计算Hash static func == (lhs: Self, rhs: Self) -> Bool { lhs.kind == rhs.kind } func hash(into hasher: inout Hasher) { hasher.combine(kind) } } struct Item: Hashable { // 每个Item新增业务唯一ID,比如后台返回的业务ID或者本地生成的UUID let id: UUID let title: String? let detailTitle: String? let subDetailTitle: String? let image: UIImage? let isActionable: Bool // 闭包不参与Hash和Equatable计算 let action: ((Any?) -> Void)? static func == (lhs: Self, rhs: Self) -> Bool { lhs.id == rhs.id } func hash(into hasher: inout Hasher) { hasher.combine(id) } }
- 保证
apply操作一定在主线程执行
所有调用apply的地方都主动派发到主队列,避免后台线程调用触发未定义行为:
func setData(sections: [TimeLogDetailViewController.Section]) { var snapshot = Snapshot() sections.forEach { section in snapshot.appendSections([section]) snapshot.appendItems(section.items, toSection: section) } // 强制主线程执行apply DispatchQueue.main.async { [weak self] in self?.dataSource?.apply(snapshot, animatingDifferences: false) } }
- 兼容低版本系统可临时关闭预估高度
iOS14及以下系统中,UITableView的预估高度和Diffable数据源配合在iPad布局变化时容易出现时序问题,可关闭预估高度验证:
tableView.estimatedRowHeight = 0 tableView.estimatedSectionHeaderHeight = 0 tableView.estimatedSectionFooterHeight = 0
内容的提问来源于stack exchange,提问作者Drugan
相关产品推荐
相关产品推荐

