Swift从Firestore获取数据:completionHandler返回空数组问题
问题根源
核心问题是Firebase的getDocument是异步执行的——你在循环里发起的所有getDocument请求还没完成、数据还没写入数组,就提前调用了completionHandler(self.gameObject),这时候数组自然是空的。
另外你这里存在冗余操作:已经通过db.collection("games").getDocuments拿到了所有文档的完整数据,完全没必要再根据name重复发起一次文档查询请求。
解决方案
方案1:移除冗余异步请求(推荐)
直接用第一次查询返回的文档数据构建对象,不需要额外发起getDocument请求,从根源避免异步顺序问题:
func fetchGameFromDB(completionHandler: @escaping ([GamesObject]) -> Void) { db.collection("games").getDocuments { (querySnapshot, err) in if let err = err { print("Error: \(err)") completionHandler([]) // 错误场景返回空数组,也可根据需求传递错误信息 return } var gameObjects: [GamesObject] = [] for document in querySnapshot!.documents { let data = document.data() let name = data["name"] as? String ?? "" let urlStanding = data["urlStanding"] as? String ?? "" let img = data["gameImg"] as? String ?? "" let urlUpcoming = data["urlUpcoming"] as? String ?? "" gameObjects.append(GamesObject(name: name, gameImg: img, urlStanding: urlStanding, urlUpcoming: urlUpcoming)) } completionHandler(gameObjects) } }
方案2:用DispatchGroup跟踪异步任务(特殊场景下使用)
如果确实需要发起多次异步请求(比如要获取关联文档数据),可以用DispatchGroup来等待所有异步任务完成后再调用completion:
func fetchGameFromDB(completionHandler: @escaping ([GamesObject]) -> Void) { db.collection("games").getDocuments { (querySnapshot, err) in if let err = err { print("Error: \(err)") completionHandler([]) return } var gameObjects: [GamesObject] = [] let group = DispatchGroup() for document in querySnapshot!.documents { if let name = document.data()["name"] as? String { group.enter() // 标记一个异步任务开始 let docRef = self.db.collection("games").document(name) docRef.getDocument { document, error in defer { group.leave() } // 不管请求成功/失败,都标记任务结束 if let document = document, let data = document.data() { let name = data["name"] as? String ?? "" let urlStanding = data["urlStanding"] as? String ?? "" let img = data["gameImg"] as? String ?? "" let urlUpcoming = data["urlUpcoming"] as? String ?? "" gameObjects.append(GamesObject(name: name, gameImg: img, urlStanding: urlStanding, urlUpcoming: urlUpcoming)) } } } } // 所有异步任务完成后,在主队列调用completion group.notify(queue: .main) { completionHandler(gameObjects) } } }
重要提示
- 异步操作不会阻塞代码执行,所以不要在异步任务外部直接依赖异步操作的结果,必须通过回调或DispatchGroup等方式确保所有异步任务完成后再处理结果。
- 尽量减少冗余的Firebase请求,既降低资源消耗,也能避免不必要的异步顺序问题。
- 错误场景下务必调用completion,避免调用方一直处于等待状态。
内容的提问来源于stack exchange,提问作者SubData
相关产品推荐
相关产品推荐

