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
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.
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
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
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.
Using Dispatch Group & Semaphore to Group iOS Async Tasks-Starter - alfianlosari/DispachGroupExample-Starter
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
DownloadTask class will be used to simulate the download of a file in the background. Here are breakdown of the class:
- It has the
TaskStateenum as the property, the 3 cases are
inProgress with int as associated value, and
completed. This will be used to manage the state of the download task. The initial state is
identifier, and the state update handler
closure. The identifier can be used to identify the task within the other tasks, while the
closurewill be invoked as a callback whenever the state gets updated.
progressvariable will be used to track the current completion progress of the download. This will be updated periodically when the download task starts.
startTaskmethod that is currently empty. We will add the code to perform the task inside a
startSleepmethod will be used to make the thread sleep for specified duration to simulate the downloading of a file.
The View Controller
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:
DownloadTasksarrays. These arrays will store all the download tasks and completed tasks. The top
table viewdisplays the current download tasks, while the bottom
table viewdisplays the completed download tasks.
SimulationOptionstruct 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.
cellForRowAtIndexPathwill dequeue the
ProgressCell, and pass the
DownloadTaskto configure the cell UI depending of the state.
tasksCountSlider. This slider will determine the number of tasks that we want to simulate in a
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 use
DispatchSemaphoreto limit the number of maximum resource that will be utilized.
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
- In the
completedstate, we just
removethe task from the
insertthe task into the
completedTasksarray index zero. The
property observerthat will trigger the
reloadDatamethod in their respective
- In the
inProgressstate, we retrieve the cell from the
cellForIndexPath:method, then we invoke the
configuremethod passing the
new state. At last, we also trigger the
endUpdatesin 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
DispatchGroup. Inside the
startOperation method, we will enumerate all the tasks and invoke the
startTask method passing the d
ispatchGroup, dispatchQueue, and dispatchSemaphore. We also pass the
randomizeTimer from the option to simulate random download time.
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
entermethod to indicates that our task execution has entered the group. We also need to balance this with the invoke of
leavemethod when the execution of our task has finished.
- We also trigger the
semaphore waitmethod to decrement the semaphore count. This also needs to be balanced with
semaphore signalmethod 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 threadin specific duration, then update the state
inProgresswith the increased progress count (0–100) until it is set to
- Whenever the state is updated, a Swift
property observerwill invoke the
task update handler closurepassing 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:
Using Dispatch Group & Semaphore to Group iOS Async Tasks - Complete - alfianlosari/GCDGroup-Complete
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
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.
Encapsulate iOS & Mac Asynchronous and Dependent Tasks into Cocoa Operation Subclass
Asynchronous tasks like fetching data from network, parsing, processing, and saving data to local cache are routine…
Let’s keep the lifelong learning and keep on building great things with Swift 😋!.