Dave is a mobile app consultant working with cutting-edge mobile app companies to increase revenue and user-retention. If you'd like to work with Dave on a contract basis, please reach out to hire@(this domain).
Follow @daveisonthegoFor more articles like this, visit my new developer site: iOS Dev Recipes
by {"twitter"=>"daveisonthego"}
Solution:
Use DispatchTime
and DispatchTimeInterval
for a readable time to wait.
Looking at the new interface for libdispatch in Swift 3, you might notice that there is a handy enum
called DispatchTimeInterval
that looks like this:
public enum DispatchTimeInterval {
case seconds(Int)
case milliseconds(Int)
case microseconds(Int)
case nanoseconds(Int)
}
But you might also notice that DispatchQueue.asyncAfter
doesn’t take this enum. Not to fear however, as when we look further in to the new libdispatch swift interface, we find these helpful methods:
public func +(time: DispatchTime, interval: DispatchTimeInterval) -> DispatchTime
public func +(time: DispatchTime, seconds: Double) -> DispatchTime
public func -(time: DispatchTime, interval: DispatchTimeInterval) -> DispatchTime
public func -(time: DispatchTime, seconds: Double) -> DispatchTime
So when you want to use these together, you might write something that looks like this:
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
// Do some work
}
And of course, if this is still too much typing, you can always add a little dollop of syntax sugar (but please just pick one):
extension DispatchTime {
/** Allow a dispatch time to be created using a `DispatchTimeInterval` since now.
Example:
DispatchTime.after(.seconds(10))
*/
public static func after(_ interval: DispatchTimeInterval) -> DispatchTime {
return .now() + interval
}
/** Allow a dispatch time to be created using a `DispatchTimeInterval` since now.
Example:
DispatchTime.in(10, interval: DispatchTimeInterval.seconds)
*/
public static func `in`(_ int: Int, interval: (Int) -> DispatchTimeInterval) -> DispatchTime {
return .now() + interval(int)
}
}
let deadline = DispatchTime.after(.seconds(10))
let altDeadline = DispatchTime.in(10, interval: DispatchTimeInterval.seconds)
Solution:
Use a DispatchWorkItem
with the .barrier
flag and a DispatchSemaphore
A less common case, but something that I’ve experienced: You want to do some heavy setup asynchronously, but that setup depends on third-party code that also has async setup.
You don’t want any work to be done until setup is complete both for your code, and your library. However, after the setup is done, work can be done in parallel, so it doesn’t make sense to have a serial queue either.
This is where a combination of a semaphore and dispatch barrier may be useful. You can create a semaphore to synchronize async work that you don’t control and wait until it’s all completed.
First, what is a dispatch barrier? If you look at the documentation in Swift, you might notice the description is:
No Description
How… helpful.
Switching over to Objective-C, we get more details:
A dispatch barrier allows you to create a synchronization point within a concurrent dispatch queue. When it encounters a barrier, a concurrent queue delays the execution of the barrier block (or any further blocks) until all blocks submitted before the barrier finish executing. At that point, the barrier block executes by itself. Upon completion, the queue resumes its normal execution behavior.
We can use this to ensure that we don’t let work be done until setup is complete while also having our dispatch queue configured to be concurrent.
Here’s a very dense example of:
// Create a custom queue that you can dispatch work to
let thirdPartyWorkQueue = DispatchQueue(label: "com.example.third-party-work-queue", qos: .utility)
// Create a dispatch work item with the `.barrier` flag
let thirdPartyLibSetup = DispatchWorkItem(flags: .barrier) {
customInitialization()
let waitForThirdPartySetup = DispatchSemaphore(value: 0)
// initialize the BlahBlah library
startBlahBlah(apiKey: "supersecret") { success in
// This is a callback *after* setup has happened on some other private
// queue, so we need to wait for it to be done
//
// `signal` sets a +1 count on our semaphore, where 0 means done
waitForThirdPartySetup.signal()
}
// Wait for the callback indicating that the other thread we don't control
// has finished doing the setup for the other library
//
// `wait` decrements the count of the semaphore, so until the signal increments it
// back to zero, this thread will be blocked.
waitForThirdPartySetup.wait()
// At this point, setup is complete but the queue we're on is still blocked
//until we finish the setup we want to do
moreCustomizationBasedOnLibrary()
// Once we reach this point, the `barrier` will stop blocking other work on the queue and it can resume being non-serial
}
thirdPartyWorkQueue.async(execute: thirdPartyLibSetup)
There’s a lot going on here, and there are definitely cases where this might not work well (for example, if the callback here was to try to dispatch back to the calling thread, we’d be deadlocked). This is a pretty complex chunk of code, so please carefully consider if it works for your use case and ideally, spend some time with the documentation of libdispatch to make sure you understand the concepts at work here.
tags: