DispatchGroup and DispatchSemaphore in Swift

Gordon Feng
Towards Dev
Published in
4 min readMar 11, 2022

--

Callback hell is like a Hadouken in your code

During our development time, we must be facing some cases that need our task executed in a specific order, in the most common requirement is to execute the task after another one.
And the most common way is to do the nested callback with Closure(in objective-C, we call it “Block”). with more closure in the nested callback, your code will more move to the right, leading to what we call “Callback hell” like below example, this kind of code is quite hard to read and maintain.

Callback hell example.

The above image is a very common example, such the case we need to reload UI after all APIs responded, is very easy to make it as a Callback hell, Currently we have lots of solutions for callback hell in these days, such as DispatchGroup and DispatchSemaphore, which we will discuss later.
Other solutions like third party framework - Promise and async/await which Apple has introduced in 2021 WWDC.
These solutions can improve the callback hell issue efficiently.

DispatchGroup

DispatchGroup is an object that can monitor all the concurrent tasks you want to track.
Through execute group.enter when the task has started and execute group.leave when the task is complete, DispatchGroup can tell you which task has been completed, so you can execute the final task via calling group.notify, for execute the final task after all the tasks you monitor have been done.

For example, if we need to reload UI after all of the API has responses, we can achieve it via using DispatchGroup.

Example of DispatchGroup

The result as below, as you can see, reload user interface has been executed after all of the APIs are finished.

fetchUserInfo(completion:) start
fetchWeatherInfo(completion:) start
loginAPI(completion:) start
fetchUserInfo(completion:) completed
fetchWeatherInfo(completion:) completed
loginAPI(completion:) completed
Reload user interface.

DispatchSemaphore

There is another useful thing called DispatchSemaphore which can design how many concurrent threads can be executed at the same time. DispatchSemaphore is like a resource control canter, by setting value of DispatchSemaphore.init(value: Int), we can tell DispatchSemaphore how many concurrent threads we want to strict to execute at the same time, by using DispatchSemaphore.wait(), will occupy resource and reduce one resource count(value -1), and when the task has done, by calling DispatchSemaphore.signal(), will release resource and recover one resource count(value +1).

Please note, the thread will be blocked if the DispatchSemaphore value is negative until .signal call enough for makes value positive.

There is two way we can use DispatchSemaphore according to Apple documentation.

  1. Passing 0 to DispatchSemaphore value for when two threads have to reconcile the completion of a specific event.
  2. Passing greater than 0 to DispatchSemaphore value to control the resource.

For the first way, we use the DispatchSemaphore by passing 0 to the value.

Start fetching user information.
Fetch user information completed.
Start reload UI.

In this example, we want to wait for fetchUserInfo completed, so we can do the reloadUI.
Of course, you can put reloadUI into fetchUserInfo to meet the requested, but in some cases, you know some code we should not put into closure for accountability reasons.
In such kind of cases, DispatchSemaphore provides another way to let us have a more flexible choice to write the code.
In line 11, we create a DispatchSemaphore and pass 0 to its value, execute fetchUserInfo in line 13 and call .wait() in line 17 to reduce 1 to value(makes value equal to -1), once the value is negative, the thread is blocked.
When fetchUserInfo is completed and call .signal(), the value will increase 1(makes value equal to 0), the thread will be unblocked, and continue to execute reloadUI in line 18.

The second way to use DispatchSemaphore, is bypassing a number greater than 0 to DispatchSemaphore value.

For example, if a user wants to download 15 files and we want to schedule download these files three by three instead download them all at the same time, we can implement with DispatchSemaphore to meet the requirement.

Result below:

Downloading 1 task
Downloading 2 task
Downloading 3 task
Download 1 task completed
Downloading 4 task
Download 4 task completed
Downloading 5 task
Download 3 task completed
Downloading 6 task
Download 2 task completed
Downloading 7 task
Download 5 task completed
Download 6 task completed
Downloading 8 task
Downloading 9 task
Download 8 task completed
Downloading 10 task
Download 9 task completed
Downloading 11 task
Download 7 task completed
Downloading 12 task
Download 11 task completed
Downloading 13 task
Download 10 task completed
Downloading 14 task
Download 14 task completed
Download 13 task completed
Downloading 15 task
Download 12 task completed
Download 15 task completed

As you can see above result, we can strict our resources, to control how many threads we can execute at the same time very easily, once a thread has been completed, the other task will continue to execute immediately, to remain three tasks activated at the same time until all the download task has finished.

If you think this article is useful to you, please give me some claps:)
thanks a lot.

--

--