SwiftUI中Firestore匹配集合获取数据仅返回单条结果问题排查
问题描述
我尝试通过以下代码从Firestore中匹配集合获取文档。由于未来用户更新信息时,新信息只会上传至"users"集合而非"clients"集合,因此选择从"users"集合拉取数据。
struct NewFriend: Identifiable, Hashable, Decodable { @DocumentID var id: String? var name: String var uid: String } struct NewProspect: Identifiable, Hashable, Decodable { @DocumentID var id: String? var name: String var uid: String } class AuthViewModel: ObservableObject { @Published var userName = "" @Published var friendName1 = "FriendName1" @Published var friendName2 = "FriendName2" @Published var friendUid1 = "friendUid1" @Published var friendUid2 = "friendUid2" @Published var matchingUid = "" let friends = [NewFriend]() let clients = [NewProspect]() func uploadInfo() { guard let uid = FireBaseManager.shared.auth.currentUser?.uid else { return } let data: [String: Any] = [ "name": self.userName, "uid": uid ] FireBaseManager.shared.firestore.collection("users").document(uid).setData(data) { error in if let error = error { print("DEBUG: Failed to store user info with error: \(error.localizedDescription)") return } } } } func addFriend() { guard let uid = FireBaseManager.shared.auth.currentUser?.uid else { return } let data: [String: Any] = [ "name": self.friendName1, "uid": self.friendUid1 ] FireBaseManager.shared.firestore.collection("users").document(uid).collections("clients").document(self.friendUid1).setData(data) { error in if let error = error { print("DEBUG: Failed to store user info with error: \(error.localizedDescription)") return } } func addFriend2() { guard let uid = FireBaseManager.shared.auth.currentUser?.uid else { return } let data: [String: Any] = [ "name": self.friendName2, "uid": self.friendUid2 ] FireBaseManager.shared.firestore.collection("users").document(uid).collections("clients").document(self.friendUid2).setData(data) { error in if let error = error { print("DEBUG: Failed to store user info with error: \(error.localizedDescription)") return } } func fetchFriends() { guard let uid = FireBaseManager.shared.auth.currentUser?.uid else { return } FireBaseManager.shared.firestore.collection("users").document(uid).collection("clients").getDocuments { snapshot, _ in guard let documents = snapshot?.documents else { return } self.friends = documents.compactMap({ try? $0.data(as: NewFriend.self) }) for i in 0 ..< self.friends.count { self.matchingUid = self.friend[i].uid print("print1: \(self.matchingUid)") FireBaseManager.shared.firestore.collection("users") .whereField("uid", isEqualTo: self.friendUid) .getDocuments { snapshot, _ in guard let documents = snapshot?.documents else { return } self.clients = documents.compactMap({ try? $0.data(as: NewProspect.self) }) print("print2: \(self.matchingUid)") } } } } } struct ListView: View { @ObservedObject var viewModel = AuthViewModel() var body: some View { List { ForEach(viewModel.clients) { client in Text(client.name) .padding() } } .onAppear { viewModel.fetchFriends() } } }
目前print1可以打印出两个好友的uid,但print2仅能打印其中一个,且列表也只显示一位好友。请问出现该问题的原因是什么?是否有更优的实现方式?
问题原因
- 异步操作与变量覆盖冲突:
matchingUid是单个字符串变量,for循环中会直接覆盖它的值。而Firestore的getDocuments是异步操作,当第一个异步请求完成时,matchingUid已经被循环的第二次迭代修改为第二个好友的uid,最终print2只会输出最后一次赋值的uid。 - 数组赋值覆盖:
clients被定义为不可变的let数组,且每次异步请求返回时都会直接用self.clients = ...覆盖整个数组,最后只有最后一次请求的结果会保留,前面的结果全部丢失。 - 语法错误:代码存在多处语法问题:
addFriend和addFriend2函数缺少闭合的}fetchFriends中self.friend[i]应为self.friends[i],whereField里的self.friendUid应为当前循环的friends[i].uidaddFriend里的collections("clients")应为collection("clients")(单数形式,获取单个子集合)
优化实现方式
1. 修正变量定义
将friends和clients改为@Published修饰的可变数组,让SwiftUI能响应数据变化:
@Published var friends = [NewFriend]() @Published var clients = [NewProspect]()
删除单个的matchingUid,改用循环内的局部变量保存当前好友的uid。
2. 异步请求的正确处理
避免循环内直接覆盖数组,改用DispatchGroup管理异步请求,完成后追加数据到数组:
func fetchFriends() { guard let uid = FireBaseManager.shared.auth.currentUser?.uid else { return } FireBaseManager.shared.firestore.collection("users").document(uid).collection("clients").getDocuments { snapshot, _ in guard let documents = snapshot?.documents else { return } self.friends = documents.compactMap({ try? $0.data(as: NewFriend.self) }) self.clients.removeAll() let dispatchGroup = DispatchGroup() for friend in self.friends { dispatchGroup.enter() let currentUid = friend.uid print("print1: \(currentUid)") FireBaseManager.shared.firestore.collection("users") .whereField("uid", isEqualTo: currentUid) .getDocuments { snapshot, _ in defer { dispatchGroup.leave() } guard let documents = snapshot?.documents else { return } if let prospect = documents.compactMap({ try? $0.data(as: NewProspect.self) }).first { DispatchQueue.main.async { self.clients.append(prospect) } print("print2: \(currentUid)") } } } } }
3. 批量查询优化(更优方案)
如果好友数量较多,多次单个查询效率低下,可改用Firestore的批量查询API,仅需一次请求:
func fetchFriends() { guard let uid = FireBaseManager.shared.auth.currentUser?.uid else { return } FireBaseManager.shared.firestore.collection("users").document(uid).collection("clients").getDocuments { snapshot, _ in guard let documents = snapshot?.documents else { return } self.friends = documents.compactMap({ try? $0.data(as: NewFriend.self) }) // 提取所有好友uid,批量查询 let friendUids = self.friends.map { $0.uid } FireBaseManager.shared.firestore.collection("users") .whereField("uid", in: friendUids) .getDocuments { snapshot, _ in guard let documents = snapshot?.documents else { return } self.clients = documents.compactMap({ try? $0.data(as: NewProspect.self) }) self.clients.forEach { print("print2: \($0.uid)") } } } }
4. 视图部分优化
使用@StateObject替代@ObservedObject(SwiftUI 2.0+推荐),避免视图重建时重新初始化ViewModel:
struct ListView: View { @StateObject var viewModel = AuthViewModel() var body: some View { List { ForEach(viewModel.clients) { client in Text(client.name) .padding() } } .onAppear { viewModel.fetchFriends() } } }
内容的提问来源于stack exchange,提问作者Ernest Tan
相关产品推荐
相关产品推荐

