如何结合UICollectionViewDiffableDataSource实现Realm数据更新?
核心问题原因
- 处理
insertions时你尝试通过datasource.itemIdentifier(for:)获取新插入索引对应的对象,但新插入的索引还没加到当前数据源中,返回必然为nil,插入逻辑无法执行 - 增量更新的逻辑方向错误:Realm返回的
deletions/insertions/modifications是最新Results和旧Results的差异索引,你通过当前快照取旧数据索引对应对象,很容易出现索引不匹配的崩溃,尤其是插入删除操作同时发生时 - 拼写错误:你定义的数据源变量是
dataSource,但闭包里调用的是datasource,大小写不一致也会触发崩溃
修复方案
方案1:全量刷新(90%场景够用,稳定无坑)
不用手动处理增量差异,每次Realm更新直接调用已有的applySnapshot()方法即可,DiffableDataSource会自动计算差异生成动画,性能完全满足常规业务需求:
fileprivate func bind() { items = realm.objects(Item.self).sorted(byKeyPath: "order").filter("isDeleted == false") notificationToken = items?.observe({ [weak self] changes in guard let self = self else { return } switch changes { case .initial, .update: self.applySnapshot() case .error(let error): fatalError("\(error)") } }) }
方案2:增量更新(适合单次更新上百条数据的大数据量场景)
需要从最新的items集合里取对应索引的对象处理,不要用旧快照的索引匹配:
notificationToken = items?.observe({ [weak self] changes in guard let self = self, let items = self.items else { return } switch changes { case .initial: self.applySnapshot() case .update(_, deletions: let deletions, insertions: let insertions, modifications: let modifications): var snapshot = self.dataSource.snapshot() // 处理删除 if !deletions.isEmpty { let deleteItems = deletions.compactMap { self.dataSource.itemIdentifier(for: IndexPath(item: $0, section: 0)) } snapshot.deleteItems(deleteItems) } // 处理插入 if !insertions.isEmpty { let insertItems = insertions.map { items[$0] } insertions.enumerated().forEach { idx, position in if position == 0 { snapshot.insertItems([insertItems[idx]], beforeItem: snapshot.itemIdentifiers.first!) } else if position >= snapshot.itemIdentifiers.count { snapshot.appendItems([insertItems[idx]], toSection: .main) } else { let targetItem = snapshot.itemIdentifiers[position] snapshot.insertItems([insertItems[idx]], beforeItem: targetItem) } } } // 处理修改 if !modifications.isEmpty { let modifyItems = modifications.map { items[$0] } snapshot.reloadItems(modifyItems) } self.dataSource.apply(snapshot, animatingDifferences: true) case .error(let error): fatalError("\(error)") } })
额外注意点
- 你的
Item类必须正确实现Hashable协议,建议用主键作为唯一判断依据,避免对象更新后hash值变化导致DiffableDataSource识别错误:
class Item: Object, Hashable { @Persisted(primaryKey: true) var id: UUID = UUID() @Persisted var name: String = "" @Persisted var order: Int = 0 @Persisted var isDeleted: Bool = false static func == (lhs: Item, rhs: Item) -> Bool { lhs.id == rhs.id } func hash(into hasher: inout Hasher) { hasher.combine(id) } }
- 页面销毁时要手动停用通知令牌避免内存泄漏:
deinit { notificationToken?.invalidate() }
内容的提问来源于stack exchange,提问作者Mohit
相关产品推荐
相关产品推荐

