Grand Central Dispatch
In Swift we have a few different options available to us when it comes to asynchronous programming — code that doesn’t immediately execute as part of our program’s main control flow, but rather is dispatched onto a different queue of execution.
Grand Central Dispatch (or GCD for short) is one such option — and is particularly interesting because it’s both an underpinning technology used throughout Apple’s various operating systems, and also an API that we as third party developers can use.
When using GCD, we’re able to execute blocks of code on various queues — which can both be queues we created ourselves, and ones provided to us by the system. One such system-provided queue is the main
queue, which is the queue that all of our UI code is (actually must be) executed on.
Here’s an example of using our app’s main queue to execute a block of code asynchronously using a closure:
var items: [Item]?
DispatchQueue.main.async {
items = findItems(matching: "News")
}
Since our code will execute asynchronously, the value of items
won’t be immediately available within the calling scope — only once our async closure has been executed will the value be assigned.
When we want to perform some form of heavy operation - such as a network request, a database query, or loading files that could potentially be quite large — it’s often better to use a background queue, rather than the app’s main one, since it lets our UI continue to function while we’re performing our task.
One option to do just that is to use a global
queue, which can optionally have a specified quality of service, that tells the system how important/urgent the code we submit onto it is. GCD then uses that information to determine how to schedule our task. Here’s what using a global queue with the background
quality of service can look like:
DispatchQueue.global(qos: .background).async {
let files = loadFiles()
process(files)
}
Now here comes the tricky part. Above, we call a process()
function, passing the files that we loaded — but we’re doing that from a background queue. That means that we need to be careful not to perform any kind of UIKit operations inside such a function, since UIKit can only be used from our app’s main queue.
However, at some point we probably need to perform some kind of UI updates in response to work we’ve done on a background queue — and the good news is that all we have to do to make that happen is to simply dispatch back to the main queue to do those updates, like this:
let label = UILabel()
// Since we’re loading content for the UI here, we use a
// higher priority quality of service for this operation.
DispatchQueue.global(qos: .userInitiated).async {
let text = loadArticleText()
// Perform all UI updates on the main queue
DispatchQueue.main.async {
label.text = text
}
}
Finally, let’s take a look at how we can create our own dispatch queues as well — which is really useful when we need a higher degree of control over a queue and the work that’s submitted onto it.
To create a dispatch queue, we simply initialize one. That’s it, once created, our new queue is immediately ready to use — just like any of the system-provided ones. Here’s two ways to initialize a queue, one using default arguments and one using custom ones:
// This queue will have the default quality of service and be
// serial — meaning that any previous work has to be completed
// before any new work will begin.
let queue = DispatchQueue(label: "CacheQueue")
// This queue has a higher priority — due to it being marked
// as ‘userInitiated’, and is concurrent — meaning that multiple
// pieces of work can be executed simultaneously.
let queue2 = DispatchQueue(
label: "ConcurrentQueue",
qos: .userInitiated,
attributes: [.concurrent]
)
Grand Central Dispatch is an incredibly powerful tool, and while submitting asynchronous closures onto different queues barely scratches the surface of what the framework is capable of — it can be a really good way to start working with asynchronous operations and concurrency using a very lightweight syntax.
Thanks for reading! 🚀