Firebase Firestore查询返回空数组,请求排查时序问题
解决Firestore异步查询导致数组为空的问题
你的推测完全正确——这就是异步代码的时序问题。Firestore的retrieve方法(包括内部的addSnapshotListener)是异步执行的:它会在后台线程发起数据请求,等数据返回后才会触发你传入的completion闭包。而你当前的getCollectionExpenses函数里,调用setExpenses()后立刻打印expenses,这时候异步请求还没完成,self.expenses自然还是空数组。
问题代码的时序拆解
我们一步步捋清楚执行顺序:
- 调用
getCollectionExpenses - 执行
setExpenses(),内部触发Firestore的异步查询请求 - 同步执行
print(expenses)和print("hello")——此时Firestore还没返回数据,expenses是空的 - 一段时间后,Firestore数据返回,
retrieve的completion闭包执行,self.expenses = expenses才会被赋值
正确的解决方法:用闭包控制异步流程
你需要把所有依赖异步数据的逻辑,放到数据返回后的回调里执行,而不是同步执行。这里有两种清晰的修改方案:
方案一:给setExpenses添加completion闭包
先修改setExpenses,让它能通知调用者数据已经准备好:
func setExpenses(completion: @escaping () -> Void) { FirebaseFunctions().retrieve(from: .expense, username: username as! String, returning: Expenses.self) { (expenses) in self.expenses = expenses completion() // 数据赋值完成,通知上层 } }
然后调整getCollectionExpenses,等待数据准备好后再处理:
public func getCollectionExpenses(collection: String, completionHandler: @escaping([Expenses], [String]) -> Void){ setExpenses { // 这里才是数据已经加载完成的时机 print(self.expenses) print("hello") var expensePerCollection = [Expenses]() for eachExpense in self.expenses { if eachExpense.collection == collection { expensePerCollection.append(eachExpense) } } // 把处理结果通过completion返回给调用者 completionHandler(expensePerCollection, /* 这里传入你需要的[String]类型参数 */) } }
方案二:直接调用retrieve,跳过中间封装
如果setExpenses的作用只是给self.expenses赋值,你可以直接在getCollectionExpenses里调用retrieve,这样能更直接地控制异步流程:
public func getCollectionExpenses(collection: String, completionHandler: @escaping([Expenses], [String]) -> Void){ FirebaseFunctions().retrieve(from: .expense, username: username as! String, returning: Expenses.self) { (expenses) in self.expenses = expenses // 直接在这里处理筛选逻辑 var expensePerCollection = [Expenses]() for eachExpense in expenses { if eachExpense.collection == collection { expensePerCollection.append(eachExpense) } } print(expenses) // 这里打印的就是有数据的数组了 print("hello") completionHandler(expensePerCollection, /* 传入对应的[String]参数 */) } }
关键注意事项
- 永远不要假设异步代码会同步完成,所有依赖异步数据的逻辑必须放到completion闭包或回调里
- 如果需要更新UI,记得用
DispatchQueue.main.async把UI操作切回主线程(Firestore的completion闭包默认在后台线程执行)
内容的提问来源于stack exchange,提问作者Arnav GUPTA
相关产品推荐
相关产品推荐

