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

如何从Firestore引用类型字段中正确获取数据并存入自定义模型?

问题根因
  • Firestore的getDocument接口是异步执行的,不会阻塞当前线程的代码执行。你发起person和messages的拉取请求后,没有等待请求返回就直接把初始化的空ChatModel加入了数组,此时person和messages字段还没被赋值。
  • 你看到的输出顺序相反就是最好的证明:主线程先执行了后续的ChatModel打印逻辑,等网络请求返回后,异步回调才执行person字段赋值和imgString打印,此时你早就把空对象加入数组了。
  • 另外你的ChatModel和Person都是值类型(struct),异步闭包捕获到的是值的拷贝,你在闭包里修改的并不是你之前定义的外层chat变量,就算异步回来赋值也不会生效。
修复方案

用DispatchGroup来管理所有异步任务的生命周期,等所有person、message的拉取请求都完成后,再初始化完整的ChatModel加入数组。
修改后的代码如下:

func fetchMessages() {
    db.collection("chat").getDocuments { snapshot, err in
        if let error = err {
            debugPrint("Error fetching documents: \(error)")
            return
        }
        guard let snap = snapshot else { return }
        // 清空旧数据,可选,根据你的业务逻辑调整
        self.chats.removeAll()
        
        for document in snap.documents {
            let data = document.data()
            let group = DispatchGroup()
            
            // 临时存储异步返回的结果
            var fetchedPerson: Person?
            var fetchedMessages: [Message] = []
            
            // 拉取person
            group.enter()
            let personRef = document.get("person") as? DocumentReference
            personRef?.getDocument { personSnapshot, personErr in
                defer { group.leave() }
                if let personError = personErr {
                    debugPrint("Error getting chat person: \(personError)")
                    return
                }
                guard let personSnap = personSnapshot, let personData = personSnap.data() else { return }
                let imgString = personData["imgString"] as? String ?? ""
                let name = personData["name"] as? String ?? ""
                fetchedPerson = Person(name: name, imgString: imgString)
            }
            
            // 拉取所有message
            let messageRefs = document.get("messages") as? [DocumentReference] ?? []
            for messageRef in messageRefs {
                group.enter()
                messageRef.getDocument { messageSnapshot, messageErr in
                    defer { group.leave() }
                    if let messageError = messageErr {
                        debugPrint("Error getting message: \(messageError)")
                        return
                    }
                    guard let messageSnap = messageSnapshot, let messageData = messageSnap.data() else { return }
                    let date = messageData["date"] as? String ?? ""
                    let text = messageData["text"] as? String ?? ""
                    let type = messageData["type"] as? String ?? ""
                    fetchedMessages.append(Message(text, type: type, date: date))
                }
            }
            
            // 所有异步任务完成后执行
            group.notify(queue: .main) {
                guard let person = fetchedPerson else { return }
                let hasUnread = data["hasUnreadMessage"] as? Bool ?? false
                let isActive = data["isActive"] as? Bool ?? false
                // 此时所有数据都已拉取完成,再初始化ChatModel
                let chat = ChatModel(
                    person: person,
                    messages: fetchedMessages,
                    hasUnreadMessage: hasUnread,
                    isActive: isActive
                )
                self.chats.append(chat)
                // 如果你需要刷新UI,在这里执行reloadData等操作
            }
        }
    }
}
关键修改说明
  • 每个chat文档的处理都创建独立的DispatchGroup,用来跟踪该chat关联的所有异步拉取任务
  • 每发起一个异步请求前调用group.enter(),请求回调结束后调用group.leave(),两者必须一一对应
  • 所有任务执行完成后,group.notify的回调会被触发,此时person和messages的数据都已准备完成,直接初始化完整的ChatModel加入数组即可,避免了值类型捕获的问题

内容的提问来源于stack exchange,提问作者Vlad Poncea

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 16:27:03