基于服务端与本地数据集更新TableView的方案是否合理,可使用TableViewDiffSource优化吗?
原实现存在的问题
你的业务逻辑目标符合需求,但现有代码存在几个严重问题:
- 未更新全局的
allNews变量:你最终拼接好的新数组存在临时变量news里,没有赋值给allNews,下次触发刷新时数据源会完全错乱 - 索引计算逻辑错误:UITableView 执行批量更新时,删除的索引是基于更新前的数据源位置计算的,你先执行插入操作、再执行删除,会导致之前计算的
deletedPaths索引完全错位,大概率会触发crash或者展示错误 - 变量名冲突:内层循环里的
let news = allNews[i]遮蔽了外层的news数组变量,可读性极差,很容易写出逻辑错误 - 依赖
News模型正确实现Hashable协议:你用Set判重的前提是News的相等判断是基于新闻唯一ID实现的,如果默认按全属性判断,会出现重复判断错误的问题
更优实现方案
UITableViewDiffableDataSource完全适用你的场景,它会自动计算新老数据源的差异,不需要你手动计算插入、删除的索引路径,从根源上避免了索引计算错误导致的各类问题,代码也更简洁易维护。
前置准备
首先确保你的News模型按唯一ID实现Hashable协议:
struct News: Hashable { let newsId: String // 其他业务属性... static func == (lhs: News, rhs: News) -> Bool { lhs.newsId == rhs.newsId } func hash(into hasher: inout Hasher) { hasher.combine(newsId) } }
配置DiffableDataSource
替换原有的手动数据源实现:
// 定义全局数据源 var dataSource: UITableViewDiffableDataSource<Int, News>! // 视图初始化时配置数据源 func setupDataSource() { dataSource = UITableViewDiffableDataSource(tableView: tableView, cellProvider: { tableView, indexPath, news in let cell = tableView.dequeueReusableCell(withIdentifier: "NewsCell", for: indexPath) as! NewsCell // 给cell赋值新闻内容 cell.config(with: news) return cell }) tableView.dataSource = dataSource }
刷新逻辑实现
拉取到新数据后的处理逻辑:
var allNews = [News]() func didFetchNews(newNews: [News]) { let newNewsSet = Set(newNews) // 过滤掉旧数据中已经出现在新返回结果里的重复项 let filteredOldNews = allNews.filter { !newNewsSet.contains($0) } // 按需求拼接成最终展示列表:新返回内容在前,去重后的旧内容在后 let finalList = newNews + filteredOldNews // 更新本地存储的全量数据 allNews = finalList // 构造快照应用到数据源,自动处理差异动画 var snapshot = NSDiffableDataSourceSnapshot<Int, News>() snapshot.appendSections([0]) snapshot.appendItems(finalList, toSection: 0) dataSource.apply(snapshot, animatingDifferences: true) }
方案优势
- 无需手动计算插入、删除的索引路径,彻底避免索引计算错误导致的crash
- 自动处理过渡动画,不需要手动调用
beginUpdates/endUpdates - 代码逻辑清晰,后续迭代维护成本更低
- 如果你不需要动画效果,把
animatingDifferences参数改为false即可直接刷新列表
内容的提问来源于stack exchange,提问作者hasan kaleli
相关产品推荐
相关产品推荐

