Let say you have two different network call function, one to get list of users and one to get list of avatars :

// Get List of Users Alamofire.request("https://httpbin.org/get?users").validate().responseJSON { response in switch response.result { case .success: print("Get User Successful") // put user into users array ... case .failure(let error): print(error) } } // Get List of Avatars Alamofire.request("https://httpbin.org/get?avatars").validate().responseJSON { response in switch response.result { case .success: print("Get Avatars Successful") // put avatar into avatars array ... case .failure(let error): print(error) } }

And you want to update the tableview showing users and avatars only after BOTH network call has finished, how do you do it?

Placing tableView.reloadData() in completion handler of either network call doesn't work as the other network call might not finish when the current one finish, making the tableView looks incomplete :

// Get List of Users Alamofire.request("https://httpbin.org/get?users").validate().responseJSON { response in switch response.result { case .success: print("Get User Successful") // put user into users array ... tableView.reloadData() // what if get avatar request havent returned yet? // the table view would look empty without avatar case .failure(let error): print(error) } }

How do you perform tableView.reloadData() only after BOTH http request is finished?

Answer : use a Dispatch Group

Apple introduced Dispatch Group in iOS 7.0 to allow us to track when multiple asynchronous tasks have completed.

We can use dispatch group to perform a function after both network request has completed like this :

// Create a dispatch group let userListDispatchGroup = DispatchGroup() // Get List of Users userListDispatchGroup.enter() Alamofire.request("https://httpbin.org/get?users").validate().responseJSON { response in switch response.result { case .success: print("Get User Successful") // put user into users array ... case .failure(let error): print(error) } // leave the dispatch group after the network request is complete and data is parsed userListDispatchGroup.leave() } // Get List of Avatars userListDispatchGroup.enter() Alamofire.request("https://httpbin.org/get?avatars").validate().responseJSON { response in switch response.result { case .success: print("Get Avatars Successful") // put avatar into avatars array .... case .failure(let error): print(error) } // leave the dispatch group after the network request is complete and data is parsed userListDispatchGroup.leave() } // after both network request complete, code inside this closure will be called // queue: .main means the main queue, always use main queue to update UI userListDispatchGroup.notify(queue: .main) { print("Both get users and get avatars has completed 👌") self.tableView.reloadData() }

The console log looks like this when the code is executed :



Pretty neat huh? Adding few lines of dispatchGroup.enter() , dispatchGroup.leave() and dispatchGroup.notify() gets the job done easily.

We will explain how dispatch group works in very simplified terms below.

How Dispatch Group works

Imagine there's an integer variable count inside a DispatchGroup object and its value is set to 0 when you initialize it. The count is used to keep track how many tasks are pending.

When you call .enter(), the count increase by 1, as 1 task is added.

When you call .leave(), the count reduce by 1, as 1 task is finished.

When the count reaches 0, meaning all task is finished, its .notify() method will be called and the closure inside will be executed.

For the Get Users and Get Avatars request we mentioned above, the dispatch group state will look like this :



It's simple to visualize and use, kudos to Apple developer team.

A few stuff to take note of

You don't necessary have to use main queue for dispatchGroup.notify(queue:) , you can use any of the DispatchQueue like background queue etc, just that to update UI usually we will use the main queue. If the .notify() method is placed before all of the .enter() method, the notify completion handler will be executed before we call .enter() , because at that point (when CPU follow line by line until the .notify() part) the count of the dispatch group is zero. eg:

let dispatchGroup = DispatchGroup() dispatchGroup.notify(queue: .main) { print("oops already called notify because at this point of code execution flow, the count of dispatch group is 0") } // oh shit already executed notify before entering dispatch group dispatchGroup.enter() Alamofire.request("https://httpbin.org/get?avatars").validate().responseJSON { response in dispatchGroup.leave() } dispatchGroup.enter() Alamofire.request("https://httpbin.org/get?users").validate().responseJSON { response in dispatchGroup.leave() }