问题
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