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 #
- Synchronous - The majority of calls you make in your apps are synchronous. This means that when you call a function, it completes everything it wants to do before returning.
- Asynchronous - In this case, you make a call to a function, it may store some kind of state, and then return quite quickly not having done what you asked it to do. It will then go off and do its task asynchronously and often call back to a delegate once it is complete.
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.
- Dispatch Queue - These are an abstraction layer that sit on top of the normal threading model. In order to make things more efficient on a mobile device Apple have introduced this idea of queues for getting things done. When we want to do something asynchronously, instead of creating a new thread to do it, we dispatch it onto a dispatch queue. The OS then gets to decide if it needs to create a new thread or if it can pop it onto an existing thread. Dispatch queues come in two varieties, serial and concurrent.
- Serial - You may be familiar with this concept from electronics, essentially in a serial queue, everything that is added to the queue gets executed in the order that it is started. When one thing finishes, the next is started and so on and so forth. This is very useful when you need to do a few things asynchronously, but need to be certain that they will run in the correct order.
- Concurrent - Back to the electronics analogy, this is somewhat akin to a parallel setup. Things that are added to a concurrent queue, will be started in the order that they are added, but will not necessarily finish in the same order. They will be executed concurrently. This is useful when we want lots of things to be done asynchronously, but we don’t really care what order they happen in, as long as they all happen.
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.
- 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.
- 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. - 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.
- 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.
- The dispatch function has returned and our
viewDidLoad
method continues and we set the text on another label in our UI. - 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 theviewDidLoad
method would have taken 10 seconds to complete, leaving the UI blank and unresponsive to the user during this time. - 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 theasync(execute:)
method of theDispatchQueue.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!