I'm a software developer based in the UK. I am blogging regularly about software development & Apple

GCD with Swift 3

As of this writing Swift 3.0 is feature complete. From here on, it's getting the version stable for its release. This also means it's time to get familiar with all the new framework APIs. The most prominent one is Grand Central Dispatch (GCD). Hence I would like to talk a little about it in this week's blog entry.

What's new in GCD with Swift3?
TL;DR: New API, new syntax, new structures and classes

So let's dive right in.

Working with DispatchQueue()

Dispatch queues are a cornerstone of GCD and their handling underwent some changes to accommodate a swiftier API. The new DispatchQueue() class is responsible for managing them. Let's create a simple serial queue:

let serialQueue = DispatchQueue(label: "com.allaboutswift.gcd.serial", attributes: [.serial], target: .global())

serialQueue.sync {
    print(" serialQueue ... ")
}

serialQueue.async {
    print(" serialQueue ... ")
}

This creates a simple serial queue with a default Quality of Service class. Making synchronous calls or asynchronous calls can easily be achieved by calling sync() or async(). Both calls come with a predefined set of default parameters. By relying on the default values, we get a very succinct interface.  What about delayed execution of enqueued closures? Well, glad you asked:

serialQueue.after(when: .now() + .seconds(1)) {
    print (" once more on serialQueue ")
}

Easy right. The first parameter is by the way of type DispatchTime(). Working with DispatchTime() is like I just showed you here: You start with an instance of DispatchTime.now() and add your seconds(), milliseconds(), microseconds() or nanoseconds() on to it.

Do you still remember dispatch_apply(). Well, it's still there and got a new name. From now on you have to call concurrentPerform() e.g.

DispatchQueue.concurrentPerform(iterations: 10) {
    print("\($0). concurrentPerform")
}

The call schedules 10 concurrent operations. 

This is the basic interface which is by design easy and succinct. The GCD interface around dispatch queues is a redesign. As a consequence of this, some familiar calls like dispatch_once() had to go. dispatch_once() was the method to choose if you wanted to execute stuff just one time only in a thread safe way e.g. initializers for Singletons. Since there is already a swifty way of doing just that (e.g. via static let sharedInstance = MySingleton()), the method became obsolete.

Apart from that, GCD classes inherit by default from DispatchObject() e.g. DispatchQueue() , DispatchGroup(), DispatchWorkItem(), DispatchSource() to name just a few. This has a few implictions. Firstly, each DispatchObject() has an active state. Most classes, like the first three just mentioned start in activated state, which means they are ready to use right away. Others like DispatchSource() have to be set active first to be useful. Secondly, DipatchObjects can be suspended or resumed if needbe via suspend() or resume(). But mind when dealing with DispatchQueues that a queue excutes all its operations first before transitioning to its suspended state.

Having said that, let's take a look at DispatchGroup()

Working with DispatchGroup()

DispatchGroup(), you guessed it, manages dispatch groups. Dispatch groups are good when treating a couple of operations as one e.g. for synchronization purposes. The old way of dealing with dispatch groups which relied on semaphores is still there:

let queue = DispatchQueue(label: "com.allaboutswift.dispatchgroup", attributes: .concurrent, target: .main)
let group = DispatchGroup()

group.enter()
queue.async (group: group) {
    print("doing stuff again")
    group.leave()
}

group.enter()
queue.async (group: group) {
    print("doing more stuff again ")
    group.leave()
}
group.notify(queue: DispatchQueue.main) {
   print("done doing stuff again")
}

Here we create a concurrent queue and a dispatchGroup to enable us to wait for the completion of all enqueued operations. But there is an easier way of doing this now as well:

queue.async (group: group) {
    print("doing stuff")
}

queue.async (group: group) {
    print("doing more stuff")
}

group.notify(queue: DispatchQueue.main) {
    print("done doing stuff")
}

Working with DispatchWorkItem()

So far we enqueued closures or operations if you will onto queues. The execution context of the closure is usually the one that was used to schedule the operation onto the queue. If you want more control or if you want to be able to cancel closures then you have to use DispatchWorkItems(). DispatchWorkItems() don't necessarily need a queue to execute on e.g.

let item = DispatchWorkItem(group: nil, qos: .utility, flags: .detached) {
    print("work item")
}
item.perform()


This executes an item on the background thread. But most of the time you might want to use them with DispatchQueue():

let queue = DispatchQueue(label: "com.allaboutswift.dispatchworkItem", attributes: .concurrent, target: .main)

queue.async(execute: item)

Easy, right? But there's more. We just talked in the previous paragraph about DispatchGroup() and synchronization. You can do this with DispatchWorkItem() as well:

queue.async(execute: item)
item.notify(queue: .main) {
    print("item executed")
}

You can even cancel them before their completion like this:

item.cancel()
item.notify(queue: .main) {
    print("item4 cancel: \(item4.isCancelled)")
}

But as you probably guessed, you then need to check for cancellation in the notification block.

Working with DispatchSource()

So far working with dispatch sources has always been cumbersome. With the redesign of GCD everything changed. Let's take a look for example of how to schedule a one shot timer:

let queue = DispatchQueue(label: "com.allaboutswift.dispatchtimer", attributes: .concurrent, target: .main)

let timer = DispatchSource.timer()
timer.scheduleOneshot(deadline: .now() + .seconds(1),leeway: .seconds(1))
timer.setEventHandler {
    print("hello world")
}
timer.activate()

What we've got now is a straighforward design. Create a DispatchSource(), set it up, define your timer handler and you are good to go. Setting up a repeating timer is pretty much the same:

timer.scheduleRepeating(deadline: .now(),interval: 1)
timer.setEventHandler {
    print("hello world")
}
timer.activate()

Mind that DispatchSource() is one of those classes, as I pointed out above, that have to be activated before their use.

If you had to fall back so far to some helper class to get an easy to use closure based timer api, the wait is over. With GCD and Swift 3 you get this now for free.

Summary

First of all, it's good to know that Swift 3 has been finalized which spares me from adapting my sources with every new XCode Beta version. Having said that, I am quite happy with the new GCD interface. In contrast to the old C-API, this one introduces new classes and value types that take advantage of Swift's strengths to enable us all to write better and more readable code.
I only introduced the basics here. There is so much more to discover. So I encourage you to try out the playground here to get familiar with the new API.

 

First impressions with server side Swift

Dispatch rules for classes & protocol extensions