How to use DispatchGroup on iOS: Practical Guide

DispatchGroup is a powerful tool that allows you to group multiple tasks together and wait for those tasks to complete before continuing with other code

Ruslan Dzhafarov
6 min readJan 21, 2023

Introduction

DispatchGroup is a powerful tool provided by Grand Central Dispatch (GCD) that allows you to group multiple tasks together and wait for those tasks to complete before continuing with other code. This can be extremely useful when working with multiple concurrent tasks that need to complete before moving on to the next step of your code. This guide will walk you through the fundamentals of using DispatchGroup with practical examples and best practices to optimize your app’s performance.

What is DispatchGroup?

DispatchGroup is a GCD feature that helps you manage a collection of tasks, allowing you to perform actions when all tasks in the group have completed. This is particularly useful when you have multiple tasks that can run in parallel but need to be synchronized at some point.

When to Use DispatchGroup?

DispatchGroup is ideal for scenarios such as:

  • Fetching data from multiple APIs concurrently and updating the UI once all data is available.
  • Performing multiple background tasks and notifying the user when all tasks are done.
  • Synchronizing the completion of several independent tasks before proceeding with the next step in your workflow.

Now that we have a basic understanding, let’s dive into the steps to use DispatchGroup in your iOS app.

Step 1: Create a DispatchGroup

First, create an instance of DispatchGroup:

// Create an instance of DispatchGroup
let dispatchGroup = DispatchGroup()

Step 2: Enter the Group

For each asynchronous task, you need to inform the group that a new task has started by calling enter():

// Inform the group that a new task has started by calling enter()
dispatchGroup.enter()

Step 3: Perform Your Asynchronous Task

In this step, we will execute an asynchronous task such as fetching data from an API using URLSession. Once the task is complete, we will call leave() to notify the DispatchGroup that the task has completed:

// Create an instance of DispatchGroup
let dispatchGroup = DispatchGroup()

// Define the URL for the API request
let url = URL(string: "https://api.example.com/data")!

// Inform the group that a new task has started by calling enter()
dispatchGroup.enter()

// Perform the asynchronous task
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
// Handle the fetched data
print("Data fetched: \(data)")
} else if let error = error {
// Handle the error
print("Error fetching data: \(error)")
}

// Notify the dispatch group that the task is complete
dispatchGroup.leave()
}

Step 4: Notify Completion

Finally, use the notify() method to execute a block of code once all tasks in the group have completed:

// Wait for the task to complete and then update the UI
dispatchGroup.notify(queue: .main) {
// Perform any follow-up tasks
print("All tasks are complete")
}

This block of code will run only after all tasks in the group have completed, allowing you to update the UI accordingly.

Practical Example

Here’s a complete example demonstrating how to use DispatchGroup to fetch data from multiple URLs concurrently and update the UI once all data is retrieved:

import UIKit

class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()

fetchMultipleData()
}

func fetchMultipleData() {
// Create an instance of DispatchGroup
let dispatchGroup = DispatchGroup()

// List of URLs to fetch data from
let urls = [
URL(string: "https://api.example.com/data1")!,
URL(string: "https://api.example.com/data2")!,
URL(string: "https://api.example.com/data3")!
]

// Array to hold the results
var results: [Data?] = Array(repeating: nil, count: urls.count)

// Loop through the URLs and fetch data
for (index, url) in urls.enumerated() {
// Enter the group before starting the task
dispatchGroup.enter()

// Perform the data task
URLSession.shared.dataTask(with: url) { data, response, error in
// Store the data in the results array
if let data = data {
results[index] = data
}

// Leave the group after the task is completed
dispatchGroup.leave()
}.resume()
}

// Notify the main queue once all tasks are complete
dispatchGroup.notify(queue: .main) {
// Update UI with results
self.updateUI(with: results)
}
}

func updateUI(with results: [Data?]) {
// Handle the data and update the UI
print("All data has been fetched and UI updated")
}
}

When the view loads, fetchMultipleData is called to start fetching data. This method creates a DispatchGroup to keep track of multiple asynchronous tasks. We define an array of URLs to fetch data from and initialize a results array to store the fetched data.

For each URL, we call dispatchGroup.enter() to signal that a task has started. Then, we use URLSession.shared.dataTask to fetch data from the URL. Once the data is fetched, we store it in the results array and call dispatchGroup.leave() to signal that the task is complete.

When all tasks are finished, dispatchGroup.notify(queue: .main) runs updateUI(with:) on the main queue to process the results and update the user interface. This ensures that the UI is only updated after all the data has been fetched.

Best Practices

Avoid Deadlocks

It’s crucial to ensure that every enter() call has a corresponding leave() call. If you forget to call leave(), the DispatchGroup will never know that the task has completed, causing your application to wait indefinitely. This can lead to deadlocks, where your code is stuck waiting for tasks that have already completed. Always balance enter() and leave() calls to avoid such issues.

Error Handling

Implement proper error handling within your asynchronous tasks. Network requests or other asynchronous operations can fail due to various reasons such as network issues, server errors, or invalid data. Ensure you handle these errors gracefully by checking for errors and responding appropriately, such as retrying the task, providing user feedback, or logging the error. This helps maintain a smooth user experience and makes debugging easier.

Main Queue Updates

Always perform UI updates on the main queue. The main queue is the only queue that can safely update the user interface. If you try to update the UI from a background queue, it can lead to unpredictable behavior or crashes. Use DispatchQueue.main.async to dispatch any UI updates to the main queue, ensuring that your app remains responsive and behaves correctly.

Timeout Handling

Consider implementing a timeout mechanism for tasks that might take too long to complete. This can be done using dispatchGroup.wait(timeout:) to specify a maximum amount of time to wait for the tasks to complete. If the timeout is reached, you can handle it appropriately, such as by canceling the remaining tasks or providing user feedback. This prevents your app from waiting indefinitely and ensures a better user experience.

Task Prioritization

Prioritize tasks when using DispatchGroup by assigning tasks to different quality of service (QoS) classes. This can help optimize your app's performance by ensuring that high-priority tasks are executed promptly. For example, use .userInitiated for tasks that the user is actively waiting for and .background for tasks that can be completed in the background without affecting the user experience.

Resource Management

Manage resources efficiently by cleaning up or releasing resources once tasks are completed. For instance, close file handles, release network connections, or remove temporary data to avoid memory leaks and ensure your app runs smoothly. Proper resource management helps maintain the performance and stability of your application.

Conclusion

DispatchGroup is a versatile tool that can significantly improve the efficiency and performance of your iOS applications by managing concurrent tasks effectively. By following the steps and best practices outlined in this guide, you can leverage DispatchGroup to synchronize asynchronous operations seamlessly and enhance the user experience in your app. Start incorporating DispatchGroup into your iOS projects today and see the difference it makes!

Thank you for reading until the end. If you have any questions, please feel free to write them in the comments.

If you enjoyed reading this article, please press the clap button 👏 . Your support encourages me to share more of my experiences and write more articles like this. Follow me here on medium for more updates!

Happy coding! 😊👨‍💻👩‍💻

More from Ruslan Dzhafarov

Advanced UIKit Techniques

9 stories

Design Patterns in Swift

9 stories

Advanced Swift Programming

3 stories

--

--

Ruslan Dzhafarov

Senior iOS Developer since 2013. Sharing expert insights, best practices, and practical solutions for common development challenges