滑动删除UITableViewCell时Firebase数据重复更新致崩溃求助
解决UITableView左滑删除后Firebase数据重复更新导致的崩溃问题
我帮你拆解下问题,你遇到的崩溃主要来自两个核心问题:删除逻辑的顺序错误和重复添加Firebase观察者导致的数据混乱,下面一步步给你修复:
一、先搞定左滑删除的逻辑顺序
你现在的代码是先删本地数组里的元素,再去删Firebase的数据,最后刷新表格。这个顺序很容易出问题:
- 如果Firebase的删除是异步完成的,这期间如果有监听Firebase数据的代码(比如同步本地数组的观察者)触发,就会和你已经修改的本地数组冲突
- 万一Firebase删除失败,本地数组已经被改了,数据就不一致了
正确的姿势是:先请求Firebase删数据,等它成功返回后,再更新本地数组并刷新表格,这样能保证本地和云端数据完全同步:
修正后的删除代码:
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { let taskToDelete = groupTask[indexPath.row] if editingStyle == .delete { // 先操作Firebase删除数据 DataService.instance.REF_GROUPS.child(group!.key).child("task").child(taskToDelete.id).removeValue(completionBlock: { (error, refer) in if let error = error { print("删除数据失败: \(error.localizedDescription)") // 这里可以加个Toast提示用户删除失败 } else { // Firebase删完了,再改本地数组,然后用deleteRows做删除动画,比reloadRows更合理 self.groupTask.remove(at: indexPath.row) self.tableView.deleteRows(at: [indexPath], with: .automatic) } }) } }
二、修复didSelectRowAt里的坑
你的点击cell代码有两个致命问题:
- 每次点击都调用
REF_GROUPS.observe(.value),这会不断添加新的持续观察者,如果点个十次,Firebase数据一变就会触发十次回调,重复更新数据、刷新表格,不崩溃才怪 cell.isSelected == true这个判断完全没用(didSelectRowAt触发时cell本来就是选中状态),而且硬写selected: true根本没法实现状态切换
修复方案:
- 用
observeSingleEvent(of: .value)代替observe(.value),因为你只是要更新状态,不需要一直监听数据变化 - 改成切换任务的选中状态(从本地数组拿当前状态,取反后更新Firebase)
- 去掉多余的全局
reloadData,只刷新当前行,既高效又避免混乱
修正后的点击和更新代码:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let selectedTask = groupTask[indexPath.row] // 切换选中状态:当前是选中就改成未选中,反之亦然 let newStatus = !selectedTask.selected DataService.instance.updateTaskStatus( desiredGroup: self.group!, selected: newStatus, childPath: selectedTask.id ) { [weak self] in // 更新本地数组的状态 self?.groupTask[indexPath.row].selected = newStatus // 只刷新当前点击的行,比全局reloadData性能好太多 self?.tableView.reloadRows(at: [indexPath], with: .automatic) } // 取消cell选中状态,用户体验更好 tableView.deselectRow(at: indexPath, animated: true) } // 顺便修正updateTaskStatus,把回调触发逻辑补对 func updateTaskStatus(desiredGroup: Group, selected: Bool, childPath: String, handler: @escaping () -> ()) { REF_GROUPS.child(desiredGroup.key).child("task").child(childPath).updateChildValues(["selected": selected]) { error, _ in if error == nil { // 更新成功再通知回调 handler() } else { print("更新状态失败: \(error!.localizedDescription)") } } }
最后再提几个注意点
- 如果你在其他地方(比如页面初始化时)用了
observe(.value)监听Firebase数据,一定要在页面销毁时调用removeAllObservers()把观察者清掉,不然会内存泄漏,还会继续触发回调搞乱数据 - 操作Firebase的时候一定要处理错误,不然出问题了连调试信息都没有
- 能不用
tableView.reloadData()就别用,优先用针对特定行的刷新方法,既快又能避免不必要的UI问题
内容的提问来源于stack exchange,提问作者Oscar
相关产品推荐
相关产品推荐

