SwiftUI中如何等待Firestore getDocuments()完成后再执行后续逻辑
解决Firestore异步调用等待问题的几种方案
我完全懂你踩的这个坑——Firestore的getDocuments是异步执行的,你的同步函数根本等不及它拉完数据就返回了空数组,这确实是新手处理异步逻辑时最容易犯的错误。下面给你几种实用的解决方案,从兼容旧版本的传统写法到现代异步语法都有,你可以根据项目的iOS版本选择:
方案1:使用闭包回调(兼容所有iOS版本)
首先修改你的getPledgesInProgress方法,不要直接返回数组,而是通过一个闭包来传递异步执行的结果:
func getPledgesInProgress(pledgePicked: Pledge, completion: @escaping ([Pledge]) -> Void) { let db = Firestore.firestore() var pledgesToReturn = [Pledge]() db.collection("Pledges") .getDocuments { (snapshot, error) in guard let snapshot = snapshot, error == nil else { // 错误处理:如果请求失败,返回空数组或你需要的默认值 completion([]) return } snapshot.documents.forEach({ (documentSnapshot) in let documentData = documentSnapshot.data() // 替换强制解包为可选绑定,避免崩溃 if let id = documentData["ID"] as? Int { pledgesToReturn.append(findPledgeWithThisID(ID: id)) } }) // 异步操作完成后,通过闭包把结果传出去 completion(pledgesToReturn) } }
然后修改initVars函数,调用这个方法并在闭包里更新你的@State变量(SwiftUI更新状态要确保在主队列,Firestore的回调默认已经在主队列,但加上DispatchQueue.main.async更稳妥):
func initVars(){ getPledgesInProgress(pledgePicked: pledgePicked ?? emptyPledge) { [weak self] pledges in DispatchQueue.main.async { self?.pledgesInProgress = pledges } } }
方案2:使用Async/Await(iOS 15+ / macOS 12+)
如果你的项目已经适配了iOS 15及以上,用Swift原生的异步语法会更简洁直观。先把getPledgesInProgress改成异步函数:
func getPledgesInProgress(pledgePicked: Pledge) async throws -> [Pledge] { let db = Firestore.firestore() // 用try await替代闭包,直接获取异步结果 let snapshot = try await db.collection("Pledges").getDocuments() var pledgesToReturn = [Pledge]() snapshot.documents.forEach({ (documentSnapshot) in let documentData = documentSnapshot.data() if let id = documentData["ID"] as? Int { pledgesToReturn.append(findPledgeWithThisID(ID: id)) } }) return pledgesToReturn }
然后在initVars里用Task来调用异步函数,更新状态:
func initVars(){ Task { do { let pledges = try await getPledgesInProgress(pledgePicked: pledgePicked ?? emptyPledge) DispatchQueue.main.async { self.pledgesInProgress = pledges } } catch { // 处理错误,比如打印日志或设置默认值 print("获取数据失败:\(error)") DispatchQueue.main.async { self.pledgesInProgress = [] } } } }
额外小贴士
- 我把代码里的
as!改成了可选绑定,这是更安全的写法,避免因为数据类型不匹配导致崩溃; - 建议把
initVars的调用放在视图的onAppear回调里,这样能保证视图出现后才开始加载数据:var body: some View { VStack{ // 你的视图内容 } .onAppear { initVars() } }
内容的提问来源于stack exchange,提问作者AAH
相关产品推荐
相关产品推荐

