如何借助Completion Handler正确遍历Firebase Query
问题
我需要遍历执行Firebase查询调用。我的循环在Firebase调用前初始化,持有所需的uid:
for uid in following_uids { }
请问如何将循环变量uid正确融入Firebase引用与查询?
for uid in following_uids { let fbRef = ref.child("users").child(uid).child("posts") // 执行Firebase查询调用 }
复现代码
func getRecentPostsFromFollowers(following_uids: [String]) { for uid in following_uids { // 外层循环 let fbRef = ref.child("users").child(uid).child("posts") fbRef.queryOrderedByKey().queryLimited(toLast: 5).observeSingleEvent(of: .value, with: {snapshot in if(snapshot.exists()){ let values = snapshot.children.compactMap { ($0 as? DataSnapshot)?.value } for postData in values { guard let restDict = (postData as AnyObject) as? [String: Any] else { continue } // 处理获取到的数据 } } }) } // 外层循环结束 print("Completion handler - Loop Done") }
伪代码
func getFollowerUIDS(completion: @escaping (_ followers: [String]) -> Void) { for uid in following_uids { let fbRef = ref.child("users").child(uid).child("posts") // 执行Firebase查询调用 } completion(value) }
环境说明
- Xcode版本:14.2 (14C18)
- 平台:iOS
解决方案
你当前的代码已经正确将循环变量uid融入Firebase引用构建中——通过ref.child("users").child(uid).child("posts")已准确指向对应用户的posts节点,这部分逻辑无问题。
但代码存在异步执行顺序问题:Firebase的observeSingleEvent是异步操作,外层循环会先执行完毕,导致print("Completion handler - Loop Done")会在所有查询完成前输出,伪代码里的completion(value)也会提前调用,无法等所有查询结果返回。
可以用DispatchGroup解决异步同步问题,具体修改如下:
修复后的getRecentPostsFromFollowers函数
func getRecentPostsFromFollowers(following_uids: [String]) { let group = DispatchGroup() for uid in following_uids { group.enter() // 标记任务开始 let fbRef = ref.child("users").child(uid).child("posts") fbRef.queryOrderedByKey().queryLimited(toLast: 5).observeSingleEvent(of: .value, with: {snapshot in defer { group.leave() } // 确保无论是否有数据,任务都标记结束 if snapshot.exists() { let values = snapshot.children.compactMap { ($0 as? DataSnapshot)?.value } for postData in values { guard let restDict = postData as? [String: Any] else { continue } // 处理获取到的帖子数据,比如存入数组、刷新UI等 } } }) } // 所有异步查询完成后执行后续逻辑 group.notify(queue: .main) { print("所有查询完成 - Loop Done") // 这里可执行刷新UI、通知外部完成状态等操作 } }
带Completion回调的版本(对应伪代码需求)
如果需要通过completion通知外部所有查询完成并返回数据,可修改为:
func getRecentPostsFromFollowers(following_uids: [String], completion: @escaping ([String: Any]?) -> Void) { let group = DispatchGroup() var allPosts = [[String: Any]]() for uid in following_uids { group.enter() let fbRef = ref.child("users").child(uid).child("posts") fbRef.queryOrderedByKey().queryLimited(toLast: 5).observeSingleEvent(of: .value, with: {snapshot in defer { group.leave() } if snapshot.exists() { let values = snapshot.children.compactMap { ($0 as? DataSnapshot)?.value as? [String: Any] } allPosts.append(contentsOf: values) } }) } group.notify(queue: .main) { completion(allPosts.isEmpty ? nil : ["posts": allPosts]) } }
关键说明
uid的正确使用:你原代码中通过child(uid)构建引用的方式完全正确,每个循环迭代都会基于当前uid生成对应的Firebase节点引用。- 异步控制:
DispatchGroup的enter()和leave()配对使用,确保每一个异步查询完成后才标记任务结束;notify()会在所有任务完成后触发,保证后续逻辑在所有数据获取后执行。 - 主线程操作:如果需要在查询完成后刷新UI,务必在
.main队列中执行,避免线程问题。
内容的提问来源于stack exchange,提问作者husharoonie
相关产品推荐
相关产品推荐

