如何避免Firebase数据加载前调用Completion Handler?代码求助
问题解决:让Completion Handler等Firebase数据全加载完再调用
嘿,我来给你掰扯明白这个事儿,保证像给5岁娃讲一样简单!
为啥现在Completion跑太早了?
你现在的代码就像:
你让5个小朋友分别去厨房拿一颗糖,刚说完让他们出发,你就立刻伸手问:“糖呢?”
这里的userRef.child(item.key).observeSingleEvent就是让小朋友跑厨房拿糖——这是异步操作,意思是小朋友不会立刻把糖给你,他们得跑一趟(Firebase服务器返回数据)才能回来。而你现在循环完所有小朋友的任务后,马上就调用completion(users),这时候所有小朋友还在半路上呢,users数组当然是空的啦!
怎么用“等待小组”(DispatchGroup)解决?
我们需要一个“小队长”来盯着所有小朋友,等所有小朋友都拿着糖回来,再喊你过来收糖。这个“小队长”就是DispatchGroup:
- 先创建一个小组:就像让小队长站好等着。
- 每个小朋友出发前,小队长记下来“哦,又出去一个”(调用
group.enter())。 - 每个小朋友拿糖回来后,告诉小队长“我回来啦”(调用
group.leave())。 - 小队长等所有人都回来,就喊你:“所有人都到齐啦,来收糖!”(调用
group.notify,在里面执行completion)。
修改后的完整代码
class func fetchFavorites(userId: String, completion: @escaping ([User]) -> Swift.Void) { let userRef = Database.database().reference().child("users") let favRef = Database.database().reference().child("favorites").child(userId) favRef.observe(.value) { snap in var users: [User] = [] // 1. 创建我们的"小队长" let dispatchGroup = DispatchGroup() for item in snap.children.allObjects as! [DataSnapshot] { // 2. 每个小朋友出发前,告诉小队长"我出去啦" dispatchGroup.enter() userRef.child(item.key).observeSingleEvent(of: .value) { snapshot in defer { // 4. 不管拿到糖没拿到,都告诉小队长"我回来啦" // defer的意思是:不管这个闭包里的代码成功还是失败,最后都会执行这行 dispatchGroup.leave() } if let dictionary = snapshot.value as? [String:Any] { let newUser = User(dictionary: dictionary) users.append(newUser) } } } // 3. 小队长等所有人回来,就喊你收糖 dispatchGroup.notify(queue: DispatchQueue.main) { completion(users) } } }
额外小提醒
- 用
defer来调用group.leave()是个好习惯,这样哪怕某个请求出错了(比如拿不到用户数据),小队长也能收到“小朋友回来”的通知,不会一直等下去。 - 我们把
completion放在dispatchGroup.notify里,而且指定了queue: .main,这样回调会在主线程执行,方便你更新UI哦!
内容的提问来源于stack exchange,提问作者p3scobar
相关产品推荐
相关产品推荐

