如何从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
相关产品推荐
相关产品推荐

