Swift中Firestore批量设置字段及自定义对象数组写入异常问题
问题分析与解决方案
为什么只保留最后一个对象?
你的代码里有两个核心问题导致了这个结果:
文档被反复覆盖
在setShiftConstraints中,你每次调用都会创建新的batch并执行setData([date : json], merge: false)。merge: false的作用是完全替换整个文档——也就是说,每次循环写入时,之前的文档内容都会被清空,只保留当前这次的date: json键值对。循环结束后,自然只剩最后一个shift的数据。DispatchGroup使用错误
你把group.leave()放在了setShiftConstraints的闭包外面,这意味着刚调用完异步的Firestore操作,就立刻标记了任务完成。DispatchGroup的notify会在所有leave执行后立刻触发,根本等不到Firestore的写入操作真正完成,所以你的返回结果判断也不准确。
正确的解决方案:一次性写入所有数据
Firestore支持一次性写入多个字段,不需要循环调用。我们可以把所有shift数据先整理成一个完整的字典,然后一次性写入文档,这样既避免了覆盖问题,又提升了效率。
步骤1:修改写入函数,支持批量对象
首先,新增一个处理多个ShiftConst对象的函数:
func setMultipleShiftConstraints<ShiftConst: Codable>(employeeId: String, shifts: [ShiftConst], completion: @escaping (Bool)->()){ do{ var shiftsDict: [String: Any] = [:] for shift in shifts { let json = try shift.toJson(excluding: [], photoString: nil) // 假设每个shift的date作为唯一键(如果date重复会覆盖,需确保唯一性) guard let dateKey = shift.date else { continue } // 根据你的ShiftConst实际属性调整 shiftsDict[dateKey] = json } // 一次性写入整个字典,merge: true可保留文档原有其他字段(按需选择) self.db.collection("shifts").document(employeeId).setData(shiftsDict, merge: true) { error in if let error = error { print(error.localizedDescription) completion(false) } else { print("All shifts uploaded!") completion(true) } } } catch let err { print(err.localizedDescription) completion(false) } }
步骤2:调整调用函数,正确处理异步操作
修改sendShiftsDataToCloud,不再循环调用写入,而是一次性传递所有shift:
func sendShiftsDataToCloud(){ // 更新shift类型 shiftConsts[0].shiftType = ShiftType(rawValue: self.shift1SegmentControl.selectedSegmentIndex)! shiftConsts[1].shiftType = ShiftType(rawValue: self.shift2SegmentControl.selectedSegmentIndex)! // 统一转换所有shift的日期格式 for shift in self.shiftConsts { shift.date = Day().convertDateFormat(from: .format1, to: .format9, date: shift.date) } // 一次性写入所有shifts self.service.setMultipleShiftConstraints(employeeId: self.service.currentUid!, shifts: shiftConsts) { [weak self] success in DispatchQueue.main.async { if success { print("finish") Alert.showAlert(title: "Success!", text: "", type: .success) } else { print("error") Alert.showAlert(title: "Error!", text: "Please try again.", type: .error) } } } }
额外场景:使用Batch批量操作
如果需要原子性操作(比如同时写入多个文档或确保所有操作要么都成功要么都失败),可以把所有更新添加到同一个Batch后再提交:
func setMultipleShiftConstraintsWithBatch<ShiftConst: Codable>(employeeId: String, shifts: [ShiftConst], completion: @escaping (Bool)->()){ do{ let batch = self.db.batch() let docRef = self.db.collection("shifts").document(employeeId) for shift in shifts { let json = try shift.toJson(excluding: [], photoString: nil) guard let dateKey = shift.date else { continue } // 使用updateData仅更新指定字段,避免覆盖整个文档 batch.updateData([dateKey: json], forDocument: docRef) } batch.commit { error in if let error = error { print(error.localizedDescription) completion(false) } else { print("All shifts uploaded via batch!") completion(true) } } } catch let err { print(err.localizedDescription) completion(false) } }
总结
- 避免循环调用
setData(merge: false),这会反复覆盖文档; - 优先一次性构建完整的字段字典,然后一次写入;
- 使用Batch时,要把所有操作添加到同一个Batch再执行commit;
- 异步操作中若用DispatchGroup,务必将
leave放在异步闭包内执行。
内容的提问来源于stack exchange,提问作者Elai
相关产品推荐
相关产品推荐

