【问题标题】:Why is this completion handler skipping a for loop为什么这个完成处理程序会跳过一个 for 循环
【发布时间】:2020-01-20 10:16:52
【问题描述】:

我有这组代码,我只想运行:self.performSegue 在所有 for 循环和 Firebase 的所有异步任务都运行完之后:

    getFeaturedPost(withCompletion: startNext)

    func getFeaturedPost(withCompletion completion: () -> Void ) {
    print("Getting featured posts...")
    ref.child("featured").child("amount").observeSingleEvent(of: .value, with: { (snapshot) in
        self.numberOfPosts = snapshot.value as! Int

        print("There's \(self.numberOfPosts) posts avaliable.")

        for pos in 0..<self.numberOfPosts{
            print("Getting reference names for post: \(pos + 1)")
            self.ref.child("featured").child("post\(pos + 1)").observeSingleEvent(of: .value, with: { (snapshot) in
                let postID = (snapshot.value as? NSDictionary)?["postID"] as? String ?? ""
                let userOfPost = (snapshot.value as? NSDictionary)?["userOfPost"] as? String ?? ""
                self.customValues.append(("/users/public/\(userOfPost)/posts/\(postID)"))
            })
        }
    })
    print("Done! The posts are: \(customValues)")
    completion()
}

func startNext()
{
    getPostData(withCompletion: {
        print("Finished getting data, going to main screen.")
        self.performSegue(withIdentifier: "showHome", sender: nil)
    })
}

func getPostData(withCompletion completion: () -> Void ) {
    print("Getting idividual post data, there are \(customValues.count) posts")
    for i in 0..<customValues.count {
        print("Currently on post: \(i)")
        let encodedURL = (customValues[i] + "/postURL")
        ref.child(encodedURL).observe(.value, with: { (snapshot) in
            if let newURL = snapshot.value as? String{
                print("Sending \(newURL) to DemoSource Class")
                DemoSource.shared.add(urlString: newURL)
            }
        })
    }
    completion()
}

然而startNext() 函数(转到下一个视图)在getFeaturedPost 开始之前执行,它是for 循环,它打印它当前所在的帖子。最后,当我使用DemoSource.shared.add(urlString: newURL) 将数据发送到演示源类时,newURL 为 nil,我有一个控制台日志,显示每个函数的打印语句的顺序:

Getting featured posts...
Done! The posts are: []
Getting idividual post data, there are 0 posts
Finished getting data, going to main screen. // This line should be printed last meaning this function is being executed too early
There's 2 posts avaliable.
Getting reference names for post: 1 // These two lines should be called before the line 'Finished getting data'
Getting reference names for post: 2

【问题讨论】:

  • print(Done 行和completion() 调用立即执行。数据库请求很晚才返回数据。在循环中使用异步任务,您需要DispatchGroup
  • @vadian 我想了很多,你能指出我应该在哪里添加 dispatchGroups,因为我知道在我做两个 firebase 查询时可能不止一个地方可以添加它们。

标签: ios swift firebase firebase-realtime-database


【解决方案1】:

DispatchGroup 的使用非常简单。这是一种计数器。 enter 增加计数器,leave 减少它。如果计数器达到0,则执行notify 中的闭包。

  • 在异步块调用enter之前的循环中。
  • 在异步块内部结束调用leave
  • 在循环调用notify之后。

    func getFeaturedPost(withCompletion completion: @escaping () -> Void ) {
        print("Getting featured posts...")
        ref.child("featured").child("amount").observeSingleEvent(of: .value, with: { (snapshot) in
            self.numberOfPosts = snapshot.value as! Int
    
            print("There's \(self.numberOfPosts) posts avaliable.")
            let group = DispatchGroup()
            for pos in 0..<self.numberOfPosts{
                group.enter()
                print("Getting reference names for post: \(pos + 1)")
                self.ref.child("featured").child("post\(pos + 1)").observeSingleEvent(of: .value, with: { (snapshot) in
                    if let post = snapshot.value as? [String:Any] {
                        let postID = post["postID"] as? String ?? ""
                        let userOfPost = post["userOfPost"] as? String ?? ""
                        self.customValues.append(("/users/public/\(userOfPost)/posts/\(postID)"))
                    }
                    group.leave()
                })
            }
            group.notify(queue: .main) {
                print("Done! The posts are: \(customValues)")
                completion()
            }
        })
    }
    

在其他方法中相应地实现一个组。

旁注:不要在 Swift 中使用 NS... 集合类型。

【讨论】:

  • 很好的答案先生 :)
  • @jawadAli 如果答案很好,请点赞 :) 评论用于要求澄清或指出帖子中的问题。
  • 我使用时出现两个错误Escaping closure captures non-escaping parameter 'completion'我需要@escaping吗?
【解决方案2】:

调度组

组允许您聚合一组任务并同步行为 在群里。您将多个工作项附加到一个组并安排 它们用于在同一队列或不同队列上异步执行。 当所有工作项完成执行时,组执行其 完成处理程序。您还可以同步等待中的所有任务 完成执行的组。 Apple Documentation

您可以使用 DispatchGroup 编辑您的方法

 func getFeaturedPost(withCompletion completion: @escaping() -> Void ) {
      let group = DispatchGroup() // create group
        print("Getting featured posts...")
         group.enter() // enter group
        ref.child("featured").child("amount").observeSingleEvent(of: .value, with: { (snapshot) in
            self.numberOfPosts = snapshot.value as! Int
    
            print("There's \(self.numberOfPosts) posts avaliable.")
    
            for pos in 0..<self.numberOfPosts{
                  group.enter() // enter group
                print("Getting reference names for post: \(pos + 1)")
                self.ref.child("featured").child("post\(pos + 1)").observeSingleEvent(of: .value, with: { (snapshot) in
                    let postID = (snapshot.value as? NSDictionary)?["postID"] as? String ?? ""
                    let userOfPost = (snapshot.value as? NSDictionary)?["userOfPost"] as? String ?? ""
                    self.customValues.append(("/users/public/\(userOfPost)/posts/\(postID)"))
                     group.leave()
                })
            }
                group.leave() // 
        })
        print("Done! The posts are: \(customValues)")
        
    }
    group.notify(queue: .main, execute: { // executed after all async calls in for loop finish
        print("done with all async calls")
        // do stuff
        completion()
    })

func getPostData(withCompletion completion:@escaping () -> Void ) {
    print("Getting idividual post data, there are \(customValues.count) posts")
let group = DispatchGroup() // create group
    for i in 0..<customValues.count {
group.enter()
        print("Currently on post: \(i)")
        let encodedURL = (customValues[i] + "/postURL")
        ref.child(encodedURL).observe(.value, with: { (snapshot) in

            if let newURL = snapshot.value as? String{
                print("Sending \(newURL) to DemoSource Class")
                DemoSource.shared.add(urlString: newURL)
            }
             group.leave()
        })
    }
    group.notify(queue: .main, execute: { // executed after all async calls in for loop finish
  
            completion()
        })
}

【讨论】:

    猜你喜欢
    • 2020-01-16
    • 1970-01-01
    • 2011-03-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多