Mastering GCD - The Basics

Intro #

The next few posts are aimed at really getting to grips with Apple’s GCD (Grand Central Dispatch), understanding what it does, how to use it, and most importantly when and why we might need to use it. If you’ve read the previous post, or perhaps you’ve used multi-threading on another platform, you’ll be familiar with the idea of an application being able to manage multiple threads in order to asynchronously perform multiple tasks. With GCD we never actually get to see a thread, Apple have provided a quite nifty abstraction in the form of dispatch queues. I’ll explain what those are in just a moment. Jargon first, lets demystify some of the jargon!

Jargon #

A good example of these two can be seen in the NSURLConnection class. You can create a synchronous connection, in which the request is made and the method call does not return until all data has been received from the server, or you can create an asynchronous connection, where the method returns immediately but without having actually made the request. Your code then continues to execute and it calls back to a delegate once it is done.

The API #

Getting a Queue #

Now let’s take a look at the APIs. First things first, we need to get hold of a dispatch queue that we can used to send our block of code to be executed on. So how do we get a queue? There are a few ways, exposed by the DispatchQueue class:

DispatchQueue.main

This class variable returns a special queue that guarantees to execute everything on the main thread. This is crucial in iOS where we can only update the UI from the main thread (more on this in a later post).

DispatchQueue.global(qos: DispatchQoS.QoSClass)

This will return one of the system’s global concurrent queues defined by their “quality of service” (QoS). You can specify one of .userInteractive, .userInitiated, .default, .utility, .background or .unspecified (not 100% sure what this one is for…) as the QoS and get the corresponding global queue. If you call this function twice in your app and pass in the same QoS, it will return a reference to the self same queue. And finally we can create our own queue if we want a bit more control over what happens when we use it, by calling the initialiser:

DispatchQueue(label: String, attributes: DispatchQueue.Attributes)

The two parameters here are a string used to identify the queue in debugging, and the attributes should be one of .serial or .concurrent which creates a queue of the specified type as described above in the jargon section. This initialiser actually has a few other parameters, but it’s quite rare to need anything other than their default values, feel free to checkout the DispatchQueue docs for more info.

Dispatching Work #

Now that we can get hold of a queue, we need to be able to send code to be executed on that queue, that’s the whole point… right?

There are two main functions on our queue that we can use for this. For simply dispatching a block of code asynchronously:

func async(execute: () -> Void)

which we can call on our queue object, and simply pass it a block of code to execute:

let myQueue = DispatchQueue.global(qos: .background)
myQueue.async(execute: {
    print("I am running on a background queue!")
}

And then there is a similar function to the above async(execute:), which allows us to synchronously dispatch a block of code (we’ll see how this can be handy shortly). You might’ve already guessed it but here it is anyway

func sync(execute: () -> Void)

Again we can simply call this on our queue object, and pass it a block of code to execute. The difference being, this function will not return until that block of code has been executed on the queue. It is worth noting that if the queue you are using is a serial queue, it operates a FIFO (first in first out) system, which means that all blocks dispatched to this queue before this call will be executed first, then this new block, and then the function returns and your code continues.

An example #

// 1
func waitForAWhile() -> Void {
    wait(10)
}

func viewDidLoad() {
    ...
    // 2
    myLabel.text = "Hello world"
    // 3
    var myQueue = DispatchQueue.global(qos: .background)
    // 4
    myQueue.async {
        // 6 (sorry about the funny ordering, it'll all make sense soon!)
        self.waitForAWhile()
        // 7
        DispatchQueue.main.sync {
            self.myLabel.text = "That was a long wait"
        }
    }
    // 5
    myOtherLabel.text = "This appears straight away!"
}

Ok, so let’s step through this.

  1. In practice this would probably be a much more exciting method but for now we have an instance method that just waits for 10 seconds. This is the method that we are going to call using GCD.
  2. In our viewDidLoad method, just because we can, we start off by setting the text on a label to "Hello world". Not to tricky thus far.
  3. Now we’re getting juicy, we get a reference to one of the global concurrent queues. In this case we get the background priority one.
  4. Now we call the dispatch function, and pass it a block of code to be executed asynchronously on that queue. This block does not get executed immediately, it just gets added to the queue, and then the function returns, hence the funny numbering.
  5. The dispatch function has returned and our viewDidLoad method continues and we set the text on another label in our UI.
  6. At some point in the future, the block of code that we dispatched get’s called. In our block we call our waitForAWhile method, which simply waits for 10 seconds. If we hadn’t dispatched this asynchronously the viewDidLoad method would have taken 10 seconds to complete, leaving the UI blank and unresponsive to the user during this time.
  7. Once the waitForAWhite method has returned we want to update our label to indicate that the wait has completed. Notice that this is wrapped in another dispatch call! As I think I keep mentioning, we cannot update the UI from anywhere other than the main thread. In order to ensure that the call to update the label is on the main thread, we call the async(execute:) method of the DispatchQueue.main queue. It’s also worth noting at this point that this second dispatch is synchronous. Since in the block we are not doing anything too intensive, we are on another queue which we’re not too bothered about blocking, and we don’t want to do anything after the dispatch, we can do it synchronously.

I’m aware that this is not a trivial topic and if something doesn’t make sense please do get in touch and I’ll try to help you out, or update the post to clarify anything that seems confusing. If you’re still interested, we’ve barely scratched the surface of the power of GCD in this post, check back for the next one to see some of the more advanced functionality that we have access to!

 
3
Kudos
 
3
Kudos

Now read this

Peek and Pop

Intro # So if you’re anything like me, a few weeks ago (at time of writing) when the new iPhone 6s came out, you spent most of the day anxiously waiting for the arrival of your new phone, which you had pre-ordered the moment it was... Continue →