如何在异类调用table.reloadData前实现Firebase更新的完成回调处理
根因分析
- 核心问题1:Cell存储了静态行索引属性
row,UITableView执行reloadData会触发Cell复用,原有Cell存储的row、postID等属性会被新的行数据覆盖,此时未执行完成的Firebase请求会读取到错误的标识,最终更新错误数据。 - 核心问题2:操作时序冲突,Firebase的
updateChildValues是异步网络请求,点击按钮后同时触发Firebase更新和列表全量刷新,Xcode 13中UITableView的Cell复用逻辑和旧版本有差异,导致该问题仅在该环境下复现。 - 额外问题:Firebase更新逻辑分散在Cell和首页视图控制器两个地方,没有统一的时序控制,放大了异步冲突的概率。
修复方案
1. 调整代理传值逻辑,移除Cell存储的静态行索引
不要依赖Cell自身存储的row、postID属性,点击时通过Cell实例实时获取当前对应的行索引,避免复用导致的数值错误:
首先修改Cell的代理协议定义:
protocol ViewControllerTableViewCellDelegate: AnyObject { // 不再传静态row,直接传Cell实例 func didTapLikeButton(on cell: ViewControllerTableViewCell, didLike: Bool) }
修改Cell内的点击触发逻辑:
@objc func likeButtonTapped() { delegate?.didTapLikeButton(on: self, didLike: !model.didLike) }
2. 统一逻辑到视图控制器,调整操作时序
将Firebase更新逻辑从Cell迁移到首页视图控制器统一管理,请求完成后再刷新UI,同时替换全量reloadData为单一行刷新,减少不必要的Cell复用:
extension homepage: ViewControllerTableViewCellDelegate { func didTapLikeButton(on cell: ViewControllerTableViewCell, didLike: Bool) { // 实时获取当前Cell对应的最新行索引,永远不会出现错位 guard let indexPath = tableView.indexPath(for: cell) else { return } // 先更新本地模型,立即给用户反馈 models[indexPath.row].didLike = didLike // 直接从数据源拿对应行的postID,不依赖Cell存储的属性 let targetPostID = people[indexPath.row].postID // 执行Firebase更新 ref.child("users").child(targetPostID).updateChildValues(updates) { [weak self] error, _ in guard let self = self else { return } DispatchQueue.main.async { if let error = error { // 请求失败回滚本地模型 self.models[indexPath.row].didLike = !didLike } // 只刷新当前点击的行,替代全量reloadData self.tableView.reloadRows(at: [indexPath], with: .automatic) } } } }
3. 移除冗余逻辑
删除Cell内buttonPressed方法中的Firebase更新逻辑,以及cellForRowAt中给cell.row赋值的代码,避免两处逻辑冲突。
额外优化建议
如果需要立刻给用户点击反馈,不需要等网络请求返回,可以先刷新当前行的UI状态,再异步执行Firebase请求,请求失败再回滚状态,体验会更流畅。
内容的提问来源于stack exchange,提问作者florida27
相关产品推荐
相关产品推荐

