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

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仅能打印其中一个,且列表也只显示一位好友。请问出现该问题的原因是什么?是否有更优的实现方式?


问题原因

  1. 异步操作与变量覆盖冲突:matchingUid是单个字符串变量,for循环中会直接覆盖它的值。而Firestore的getDocuments是异步操作,当第一个异步请求完成时,matchingUid已经被循环的第二次迭代修改为第二个好友的uid,最终print2只会输出最后一次赋值的uid。
  2. 数组赋值覆盖:clients被定义为不可变的let数组,且每次异步请求返回时都会直接用self.clients = ...覆盖整个数组,最后只有最后一次请求的结果会保留,前面的结果全部丢失。
  3. 语法错误:代码存在多处语法问题:
    • addFriend和addFriend2函数缺少闭合的}
    • fetchFriends中self.friend[i]应为self.friends[i],whereField里的self.friendUid应为当前循环的friends[i].uid
    • addFriend里的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 11:05:41