You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift中Firestore批量设置字段及自定义对象数组写入异常问题

问题分析与解决方案

为什么只保留最后一个对象?

你的代码里有两个核心问题导致了这个结果:

  1. 文档被反复覆盖
    在setShiftConstraints中,你每次调用都会创建新的batch并执行setData([date : json], merge: false)。merge: false的作用是完全替换整个文档——也就是说,每次循环写入时,之前的文档内容都会被清空,只保留当前这次的date: json键值对。循环结束后,自然只剩最后一个shift的数据。

  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 18:42:30