删除已更新状态的UITableViewCell时应用崩溃问题求助
看起来你的崩溃是两个核心问题叠加导致的:Firebase数据残留引发的类型转换错误,以及数据监听和删除操作的并发冲突。咱们一步步拆解解决:
1. 定位崩溃根源
报错Could not cast value of type 'NSNull' to 'NSString'说明,在解析Firebase返回的任务数据时,你尝试把NSNull(字段不存在时Firebase返回的默认类型)强制转换成NSString。为什么会出现这种情况?因为删除任务后,Firebase里该任务节点居然只剩下selected字段,其他核心字段(比如taskToDo、peopleToDoTask)都消失了——这是因为你的监听逻辑有问题,删除后还在触发更新操作把selected重新写回去。
2. 修复数据监听的并发问题
在didSelectRowAt方法里,你用了REF_GROUPS.observe(.value),这是持续监听,每次点击cell都会新增一个监听实例。当你删除任务时,之前的监听可能还在触发,导致updateTaskStatus又把selected字段写入已删除的节点,最终这个节点只有selected,其他字段都是NSNull。
把持续监听改成单次监听:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { guard let cell = tableView.cellForRow(at: indexPath) as? GroupTaskCell else { return } let selectedTask = groupTask[indexPath.row] // 建议用模型里的状态反转,而不是依赖cell的isSelected(点击后它本来就是true) let newStatus = !selectedTask.selected // 替换observe(.value)为observeSingleEvent,只获取一次数据 DataService.instance.REF_GROUPS.observeSingleEvent(of: .value) { _ in DataService.instance.updateTaskStatus( desiredGroup: self.group!, selected: newStatus, childPath: selectedTask.id, handler: { _ in self.tableView.reloadData() } ) } }
3. 处理NSNull的类型转换安全问题
在你的Task模型解析代码里(你没贴出来,但肯定存在这一步),不要用强制转换as!,改用可选绑定+默认值,避免NSNull触发崩溃:
// 假设这是你的Task初始化方法 init(snapshot: DataSnapshot) { self.id = snapshot.key // 用as? + 默认值处理NSNull情况 self.taskToDo = snapshot.childSnapshot(forPath: "taskToDo").value as? String ?? "" self.peopleToDoTask = snapshot.childSnapshot(forPath: "peopleToDoTask").value as? String ?? "" self.senderId = snapshot.childSnapshot(forPath: "senderId").value as? String ?? "" self.selected = snapshot.childSnapshot(forPath: "selected").value as? Bool ?? false }
4. 优化删除逻辑,保证本地与远程数据同步
你现在是先删本地数组再删Firebase数据,这可能导致数据不一致。应该在Firebase删除成功后,再更新本地数组并刷新表格:
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { let taskToDelete = groupTask[indexPath.row] if editingStyle == .delete { DataService.instance.REF_GROUPS.child(group!.key).child("task").child(taskToDelete.id).removeValue { error, _ in if let error = error { print("删除失败:\(error.localizedDescription)") return } // 远程删除成功后,再更新本地数据 self.groupTask.remove(at: indexPath.row) self.tableView.deleteRows(at: [indexPath], with: .fade) // 用deleteRows比reloadData更高效,也避免索引混乱 } } }
5. 简化冗余的方法重载
你的updateTaskStatus有两个重载,但其中一个的atIndexpath参数根本没用到,直接合并简化:
func updateTaskStatus(desiredGroup: Group, selected: Bool, childPath: String, handler: @escaping (_ success: Bool) -> ()) { REF_GROUPS.child(desiredGroup.key).child("task").child(childPath).updateChildValues(["selected": selected]) { error, _ in handler(error == nil) } }
这样修改后,删除已标记的任务就不会再崩溃了,数据同步也会更可靠。
内容的提问来源于stack exchange,提问作者Oscar

