When you need to make sure that various threads wait for the availability of a shared resource (whether that be a file, the result of an API query, etc.) you can use GCD's DispatchSemaphore feature.
The simplest example:
// Declared as a property
let semaphore = DispatchSemaphore(value: 1)
/// Then in a function...
func doSomething() {
self.semaphore.wait() // requesting the resource
// Do something here
self.semaphore.signal() // releasing the resource
}
The value part of the DispatchSemaphore defines how many threads can access the resource at once. When you wait(), this requests the resource (so adds one to the counter) and signal() releases (or subtracts one from the counter). Whenever another thread hits wait() it will check if the counter is >= than value...if it is, it will wait until the resource is released.
So in the example above, the result is that you can be sure that // Do something here is only called by one thread at any given time. If multiple threads hit this code they will all wait for any previous thread to complete before continuing.
On the basis of this great post on Medium here is a more advanced example:
let higherPriority = DispatchQueue.global(qos: .utility)
let lowerPriority = DispatchQueue.global(qos: .utility)
let semaphore = DispatchSemaphore(value: 1)
func asyncPrint(queue: DispatchQueue, symbol: String) {
queue.async {
print("(symbol) waiting")
semaphore.wait() // requesting the resource
for i in 0...10 {
print(symbol, i)
}
print("(symbol) signal")
semaphore.signal() // releasing the resource
}
}
asyncPrint(queue: higherPriority, symbol: "A")
asyncPrint(queue: lowerPriority, symbol: "B")
A waiting
B waiting
A 0
A 1
A 2
A 3
A 4
A 5
A 6
A 7
A 8
A 9
A 10
A signal
B 0
B 1
B 2
B 3
B 4
B 5
B 6
B 7
B 8
B 9
B 10
B signal
You can also make threads wait until async operations complete - this will allow you to write a non-escaping, synchronously returning function even though it performs async operations within its logic. To do this, you must use Semphores with a value of 0 to not allow any further execution until the signal is reached:
let semaphore = DispatchSemaphore(value: 0)
var result: Bool? = nil
DoSomething.Async { (asyncResult)
// Do whatever you need
result = asyncResult
// Now we emit a signal
semaphore.signal()
}
// Here we wait until the async operation completes
// (It may be a good idea to add a timeout in case your async takes too long or fails)
let waitResult = semaphore.wait(timeout: DispatchTime.now() + .seconds(5))
guard case .timedOut = waitResult else {
// Timed out!
return
}
// Result is ready to use!
print(result)
Federico's Medium article does a great job at discussing these issues. In short: