Using Dispatch Group & Semaphore to Group iOS Async Tasks

You can also read this article in my Xcoding With Alfian blog website using the link below.
Grand Central Dispatch (GCD) is a framework provided by Apple that was released in 2009 with OS X Snow Leopard & iOS 4. It provides easy to use API for the developers to to run background tasks by creating queue in serial
or concurrent
style without managing threads by themselves.
GCD abstracts the assignment of threads for computation into dispatch queue
. Developers only need to create their own dispatch queue
or they can use Apple provided built in global dispatch queue
with several built-in Quality of Service (QoS)
from user interactive, user initiated, utility, and background.
GCD will handle the thread assignment in a thread pool
automatically.
There are some instances when we as a developer need to perform multiple batch asynchronous tasks
in the background
, and then receive the notification
when all the job is completed
in the future
. Apple provides DispatchGroup
class that we can use to do this kind of operation. Here are the brief summary of what the DispatchGroup is by Apple.
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.
While the DispatchGroup
can also be used to wait synchronously for all tasks in the group to finish executing, we won’t doing this in this tutorial.
We’ll also will use the DispatchSemaphore
to limit the number of concurrent simultaneous tasks that we execute in a queue. According to Apple DispatchSemaphore
is:
An object that controls access to a resource across multiple execution contexts through use of a traditional counting semaphore.
Here are also some of the real use cases that we might encounter as an developer:
- Performing multiple network requests that depends on the completion of all the other requests before continue.
- Performing multiple videos/images processing tasks in the background .
- Download/upload of files in the background simultaneously.
What we will build
We will be exploring on how we can leverage DispatchGroup
& DispatchSemaphore
by creating a simple project that will perform a simulation of simultaneous background downloads, when all the download tasks completed successfully, we will display successful alert message in the UI. It also has several capabilities such as:
- Set the total number of download tasks.
- Randomize the download time of each task.
- Set how many concurrent tasks can be run a queue simultaneously.
To begin, please download the starter project from the GitHub repository link below.
The Starter Project
The starter project contains all the view controller
, cells
that has been prepared before so we can focus on how to use the dispatch group
& dispatch semaphore
. We will simulate the action of performing multiple downloading of files in the background by using the dispatch group
. We’ll also look into how we can use dispatch semaphores
to limit the number of simultaneous concurrent download to a specific number.
The Download Task
The DownloadTask
class will be used to simulate the download of a file in the background. Here are breakdown of the class:
- It has the
TaskState
enum as the property, the 3 cases arepending
,inProgress with int as associated value
, andcompleted
. This will be used to manage the state of the download task. The initial state ispending
. - The
intializer
accepts anidentifier
, and the state update handlerclosure
. The identifier can be used to identify the task within the other tasks, while theclosure
will be invoked as a callback whenever the state gets updated. - The
progress
variable will be used to track the current completion progress of the download. This will be updated periodically when the download task starts. - The
startTask
method that is currently empty. We will add the code to perform the task inside aDispatchGroup
withsemaphore
later. - The
startSleep
method will be used to make the thread sleep for specified duration to simulate the downloading of a file.
The View Controller

The main JobListViewController
consists of 2 table view
, one is for displaying download tasks and one is for is displaying completed tasks. There are also several sliders and a switch that we can configure. Here are the breakdown of the class:
- The
CompletedTasks
andDownloadTasks
arrays. These arrays will store all the download tasks and completed tasks. The toptable view
displays the current download tasks, while the bottomtable view
displays the completed download tasks. - The
SimulationOption
struct where we store the configuration of the app such as the number of tasks, whether the download time is randomized, and number of simultaneous concurrent tasks in a queue. - The
TableViewDataSource
cellForRowAtIndexPath
will dequeue theProgressCell
, and pass theDownloadTask
to configure the cell UI depending of the state. - The
tasksCountSlider
. This slider will determine the number of tasks that we want to simulate in adispatch group
. - The
maxAsyncTasksSlider
. This slider will determine the number of maximum concurrent tasks that will run in a dispatch group. For example, given a 100 download tasks, we want our queue to proceed only 10 downloads simultaneously. We will useDispatchSemaphore
to limit the number of maximum resource that will be utilized. - The
randomizeTimeSwitch
. This switch will determine whether to randomize the download time of each download task.
Let’s begin by simulating the operation when user taps on the start
bar button item that will trigger the startOperation
selector that is currently empty.
Creating DispatchQueue, DispatchGroup, & DispatchSemaphore
To create all the the instances of those 3 we can just instantiate them with their respective class, then assign it to a variable like so. The DispatchQueue
intializer is given a unique identifier (using a reverse domain dns
name as the convention), then we set the attributes to concurrent
so we can perform multiple jobs in parallel
asynchronously. We also set DispatchSemaphore
value using the maximumAsyncTaskCount
to limit the number of simultaneous download task. At last, we also make sure to disable the user interaction
of all the buttons, sliders, and switch when the operation starts.
Creating The Download Tasks & Handling State Update
Next, we just create the tasks based on the number of the maximumJob
count from the option property. Each DownloadTask
is instantiated with an identifier, then in the task update state closure
, we pass the callback
. Here are the breakdown of the callback implementation:
- Using the task identifier, we retrieve the index of the task from
downloadTask
array. - In the
completed
state, we justremove
the task from thedownloadTasks
array, theninsert
the task into thecompletedTasks
array index zero. ThedownloadTasks
andcompletedTasks
has aproperty observer
that will trigger thereloadData
method in their respectivetable view
. - In the
inProgress
state, we retrieve the cell from thedownloadTableView
using thecellForIndexPath:
method, then we invoke theconfigure
method passing thenew state
. At last, we also trigger thetableView beginUpdates
andendUpdates
in case the the height of the cell changed.
Starting the Task Operation into DispatchGroup with DispatchSemaphore
Next, we will start the downloading of task by assigning the job into the DispatchQueue
and DispatchGroup
. Inside the startOperation
method, we will enumerate all the tasks and invoke the startTask
method passing the dispatchGroup, dispatchQueue, and dispatchSemaphore
. We also pass the randomizeTimer
from the option to simulate random download time.
In the DownloadTask startTask
method, we invoke the dispatchQueue async
method passing the dispatch group
. In the execution closure, here are the things we will do:
- Invoke the group
enter
method to indicates that our task execution has entered the group. We also need to balance this with the invoke ofleave
method when the execution of our task has finished. - We also trigger the
semaphore wait
method to decrement the semaphore count. This also needs to be balanced withsemaphore signal
method when the task has finished to increment the semaphore count so it can execute another task. - Between those calls, we will perform a simulation of the download by
sleeping the thread
in specific duration, then update the stateinProgress
with the increased progress count (0–100) until it is set tocomplete
. - Whenever the state is updated, a Swift
property observer
will invoke thetask update handler closure
passing the task.
Using DispatchGroup notify to receive notification of completed jobs
At last, to receive the signal when all the background download tasks has been completed, we need to invoke the group notify
method passing the queue
and the closure that will be invoked when all the download has been finished.
Inside the closure, we just invoke the present alert method passing the completion message. Finally, we need to make sure to enable all the button, sliders, and switch user interaction
property back.
Try to build and run the project. Play with all the sliders and switch to see the different behaviors of the app based on the number of tasks, simultaneous running task, and simulated download timer.
You can download the completed project GitHub repository from the link below:
Conclusion
That’s it!. As the next release evolution of Swift to utilize async await to perform asynchronous job, GCD still provides the best performance for us when we want to write an asynchronous job in the background. With DispatchGroup
and DispatchSemaphore
we can group several tasks together, perform the job in queue we want, and get notification when all the tasks has been completed.
Apple also gives us an option to use higher level abstraction using OperationQueue
to perform async tasks. It has several advantages such as suspending, adding dependencies between tasks. You can learn more about it from this article.
Let’s keep the lifelong learning and keep on building great things with Swift 😋!.