为啥这个完成处理程序会跳过一个 for 循环

Posted

技术标签:

【中文标题】为啥这个完成处理程序会跳过一个 for 循环【英文标题】: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 查询时可能不止一个地方可以添加它们。 【参考方案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()
        )

【讨论】:

以上是关于为啥这个完成处理程序会跳过一个 for 循环的主要内容,如果未能解决你的问题,请参考以下文章

为啥程序跳过while循环

如果处理了错误,while 循环会跳过一个循环。我怎样才能让它在周期的其余部分运行?

为啥 .setValue() 在 for 循环中跳过列?

当堆栈仍然有元素时,为啥会跳过“如果堆栈不为空”条件?

为啥会跳秒??倒计时器

为啥 std::next_permutation 会跳过一些排列?