How do you run code after an async call finishes a

2019-09-15 14:26发布

问题:

I have an array containing post IDs, then I do a for loop and retrieve each post content and append the post to the post array. After the loop ends, I need to return the post array in the completion handler.

However, the .observeSingleEvent is an async method, therefore the posts contents are retrieve from Firebase, the completionhandler(posts) is already executed, which returns an empty array. I want to return the array after every post is appended to the array, similar to making these methods synchronous.

static func getPost(postKeys:[String], completionHandler: @escaping ([Post])->()){
    var posts = [Post]()
    for postKey in postKeys{
        let postRef = DataService.ds.REF_POSTS.child(postKey)
        postRef.observeSingleEvent(of: .value, with: { (snapshot) in
            if let postDict = snapshot.value as? Dictionary<String, Any>{
                //create a post and append it to the post array
                let key = snapshot.key
                let post = Post(postKey: key, postData: postDict)
                posts.append(post)
            }
        })
    }
    completionHandler(posts)
}

回答1:

You can use a DispatchGroup to execute some code when a series of tasks is complete.

Call the enter function before you start a task and the leave function once the task is complete. You can use notify to provide a closure that will be executed once all tasks have 'left' the DispatchGroup.

You also need to be careful to avoid concurrency issues with updating the array; Swift arrays are not thread-safe. Executing the array update on a serial dispatch queue can fix this.

static func getPost(postKeys:[String], completionHandler: @escaping ([Post])->()){
    var posts = [Post]()
    let dispatchGroup = DispatchGroup()
    let arrayQueue = DispatchQueue(label: "arrayQueue")
    for postKey in postKeys{
        dispatchGroup.enter()
        let postRef = DataService.ds.REF_POSTS.child(postKey)
        postRef.observeSingleEvent(of: .value, with: { (snapshot) in
            if let postDict = snapshot.value as? Dictionary<String, Any>{
                //create a post and append it to the post array
                let key = snapshot.key
                let post = Post(postKey: key, postData: postDict)
                arrayQueue.sync {
                    posts.append(post)
                }
            }
            dispatchGroup.leave()
        })
    }

    dispatchGroup.notify(queue: DispatchQueue.main) { 
        completionHandler(posts)
    }
}