Why is this completion handler skipping a for loop

守給你的承諾、 提交于 2020-06-27 04:25:06

问题


I have this group of code, I only want to run: self.performSegue AFTER all of the for loops and all of the Firebase's asynchronous tasks have finished running:

    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()
}

Yet the startNext() function (which goes to the next view) is executed before getFeaturedPost's starts it's for loop where it prints what post it'c currently at. By the end when I send the data to the demosource class with DemoSource.shared.add(urlString: newURL) the newURL is nil, I have a console log which shows you the order of the print statements of each function:

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

回答1:


The usage of DispatchGroup is very easy. It's a kind of a counter. enter increments the counter, leave decrements it. If the counter reaches 0 the closure in notify is executed.

  • In the loop before the asynchronous block call enter.
  • Inside the asynchronous block at the end call leave.
  • After the loop call 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()
            }
        })
    }
    

Implement a group accordingly in the other method.

Side note: Don't use NS... collection types in Swift.




回答2:


DispatchGroup

Groups allow you to aggregate a set of tasks and synchronize behaviors on the group. You attach multiple work items to a group and schedule them for asynchronous execution on the same queue or different queues. When all work items finish executing, the group executes its completion handler. You can also wait synchronously for all tasks in the group to finish executing. Apple Documentation

You can edit your methods using 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()
        })
}


来源:https://stackoverflow.com/questions/59821135/why-is-this-completion-handler-skipping-a-for-loop

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!