Using semaphores in iOS to serialise callback completion
Tags: ios, swift, objc
Published: 2021-11-12 12:19:06

---
title: Using semaphores in iOS to serialise callback completion
published: true
date: 2021-11-12 10:11:57 UTC
tags: ["ios", "swift", "objc"]
canonical_url: https://tarikdahic.com/posts/using-semaphores-in-ios-to-serialise-callback-completion/
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1mmxtbxjloenpxbxu1fa.jpg
---

In most of the situations when you are developing apps you will encounter APIs provided to you by someone else (framework, library, system, etc.). You cannot change the API and you need to use it as it is. Let’s say, to use that API, you give it a function that will be called when the result is ready or some action is completed (also known as a _callback_).

But, what if you don’t want to continue if the result is not ready or action is not completed? I’ve found that using **semaphores** is good for this situation and I will describe how to do it in this article. But first, we need to learn more about semaphores.

## Semaphore 🚦

Semaphore mechanism was proposed by Dijkstra in the sixties which is a very significant technique to manage concurrent operations on shared resources. A trivial semaphore is a variable, like _integer_ or a _boolean_ that is incremented/decremented or toggled and depending on the state of that variable threads can access the resource or wait for it to become free.

A useful way to think of a semaphore as used in a real-world system is as a record of how many units of a particular resource are available, coupled with operations to adjust that record _safely_ (i.e., to avoid [race conditions](https://en.wikipedia.org/wiki/Race_condition)) as units are acquired or become free, and, if necessary, wait until a unit of the resource becomes available.

There are two types of semaphores:

- **Binary Semaphore** — It can have only two values – 0 and 1, and we can also represent this semaphore with a boolean. It will allow only one thread to use the resource while others will wait.
- **Counting Semaphore** — By creating this semaphore we provide it with a number of how many threads are allowed to use it at the same time. When threads use the resource the semaphore counter is decremented. If the counter reaches 0 the threads that want to access the resource will wait until one of the threads is finished.

If we think of the implementation, a simple semaphore could be implemented by using one **integer** variable and one **FIFO queue**. Initial count of the integer variable determines how many available resources we have for consumers to use. If someone asks for access we decrement the **count** variable and if the value is not negative we allow the access to the resource. If the value is negative we add that consumer to FIFO queue and halt it until one of the consumers that is already using the shared resource signals that it has finished. When the thread **signals** that it has finished using the shared resource we increment the count variable and if threads are waiting in the FIFO queue we use the first one and give it access to the shared resource. Implementation like this where there is no prioritisation of threads can lead to a problem called [priority inversion](https://en.wikipedia.org/wiki/Priority_inversion).

The operation that consumers use to request access of the shared resource which decrements the count variable is called **wait** and the operation of incrementing the count variable and telling the semaphore that we’re done using the shared resource is called **signal**.

Another great analogy that I found is:

> Semaphore is the number of free identical toilet keys. Example, say we have four toilets with identical locks and keys. The semaphore count — the count of keys — is set to 4 at beginning (all four toilets are free), then the count value is decremented as people are coming in. If all toilets are full, ie. there are no free keys left, the semaphore count is 0. Now, when eq. one person leaves the toilet, semaphore is increased to 1 (one free key), and given to the next person in the queue.
> 
> Officially: “A semaphore restricts the number of simultaneous users of a shared resource up to a maximum number. Threads can request access to the resource (decrementing the semaphore), and can signal that they have finished using the resource (incrementing the semaphore).”_Ref: Symbian Developer Library_

## Semaphores in iOS

In iOS semaphore implementation is a part of the `Dispatch` framework. You can use semaphores with _Obj-C_ and _Swift_. I will provide an example for both languages.

The API to use semaphores is not complicated and it is very _straightforward_. We have 3 operations: _create, wait_ and _signal_. In semaphore implementation for iOS the _wait_ operation can be requested with a **timeout** to limit how much we want to wait to resource to become free. I advise to always provide a timeout so that the threads don’t become blocked.

To create the semaphores we can use the code below:

```swift
// Swift
let semaphore = DispatchSemaphore(value: 2)

// Obj-C
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
```

Now, if we want to **wait** and **signal** we can use the operations below:

```swift
// Wait - Swift
semaphore.wait(timeout: .now() + 5)
// Wait - Obj-C
dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)));

// Signal - Swift
semaphore.signal()
// Signal - Obj-C
dispatch_semaphore_signal(semaphore);
```

## Serialising callback completion

While dealing with some async APIs that take completion handlers I’ve run in to a problem where I need to wait for the execution of that API call to complete so that I can continue with the execution of my program. I didn’t want to end the function that is currently running and I used semaphores to wait for the async API to complete.

This has proven very useful to me when I work on _iOS App Extensions_ that run in the background and that have very limited time of execution. While dealing with `UNUserNotificationCenter` that provides async operations this technique helped me a lot. So, without further ado, let’s go to the example.

In the code below I have a function that takes another function as a parameter and runs it after _the work is completed_. This is just a simulation of what other real-world APIs provide.

```swift
func doSomethingAndNotifyMe(via completion: @escaping () -> Void) {
    DispatchQueue.global().async { // some work is being done here :)
        sleep(3)
        completion()
    }
}
```

I will use `doSomethingAndNotifyMe` in the example below to see what happens and how my code executes:

```swift
print("1. Starting the work")

doSomethingAndNotifyMe {
    print("2. Work done")
}

print("3. End of the work")
```

After running the example above, I get this output:

```
1. Starting the work
3. End of the work
2. Work done
```

You can see that the output messages are not in the order I want them to be, they are ordered as 1, 3, 2 and I want the order to be 1, 2, 3. To have the order that I want I will introduce semaphore to the example above:

```swift
print("1. Starting the work")
let semaphore = DispatchSemaphore(value: 0)

doSomethingAndNotifyMe {
    print("2. Work done")
    semaphore.signal()
}

semaphore.wait(timeout: .now() + 5)
print("3. End of the work")
```

When I run the example with the semaphore I get the output that I want:

```
1. Starting the work
2. Work done
3. End of the work
```

You can see that I initialised the semaphore with the value `0`. This is not a practice that is commonly seen and I like to think of this type of a semaphore as a **blocking semaphore**. You can think of it as using a lock where the default state of the lock is _locked_.

Wait operation can be **dangerous** so don’t use it on the main thread and always try to provide the **timeout** for it.

Although this might not be the best practice for this kind of situations it has really proven to work great. A modern alternative to using semaphores for examples like this is using `DispatchGroup` API from the `Dispatch` library. `DispatchGroup` is only available on Apple platforms while semaphore implementations are available for almost every platform.

Have you used semaphores in your projects or any other mechanisms for dealing with multi-threaded environments? Please share your experiences and thank you for reading this article.