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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 03:03:20