Grand Central Dispatch (GCD)
GCD is an open source framework made available by apple for both iOS and OSX.
GCD makes concurrent programming easier by abstracting away the the logic that handles threads into queues of closures.
GCD is written in C
, not Swift
Queues
GCD provides use with two types of queues, serial which are synchronous and concurrent which are asynchronous.
In the serial queue every closure is run when it reaches the end of the queue. This type of queue is predictable in that we know the order in which each closure will be run.
Concurrent queues have several threads that pick up closures at any point in the queue and runs them. This type of queue is unpredictable because we don't know the order or when each closure will be run.
MainQueue
An app can have several queues, but not all queues are created equally. There is a special queue called the MainQueue
. This is the queue that handles the UI of the app and any of the code that does not explicitly ask to run in a background queue.
We can access the MainQueue
with DispatchQueue.main
.
Blocking
It is very important that we never run code that blocks the MainQueue
. Blocking occurs when code is executed in the queue for any human perceivable amount of time and prevents the app from continuing until it's done. We might block the MainQueue
by doing something very computationally expensive like applying a filter to a video file or a large image.
The most common reason the MainQueue
gets blocked by implementing calls to external API's. We cannot allow the MainQueue
to wait for the network to responds before continuing. If this happens the UI will be slow and the app will most likely be rejected from the App Store.
Global Queues
iOS gives every app 4 extra queues on top of the main one called global queues.
DispatchQoS.QoSClass.background
DispatchQoS.QoSClass.utility
DispatchQoS.QoSClass.userInteractive
DispatchQoS.QoSClass.userInitiated
We can access these queues with DispatchQueue.global(qos:)
. The only difference between them is the priority of how fast they will get the attention of the CPU. It's a good practice to avoid creating new queues when reusing an existing one is enough.
Adding closures to a queues
We can add a closure to a queue with async
. This function takes a queue and a closure, adds the closure to the queue and returns immediately. The code inside the queue will run sometime in the future, but the exact time will depend on how many closures are in the queue.
Notes on UIKit and Core Data
We can't run anything from UIKit
in the background or the app could crash intermittently. This is a concurrency issue, so sometimes it will show up and sometimes it won't making debugging difficult. A way to remember this is to never run anything that ends with "view" in the background.
Here is an example of some code where we access UIKit
in the background - this code has a concurrency bug and will only crash some of the time, but not always.
// create a queue
let downloadQueue = DispatchQueue(label: "download")
// add a closure that encapsulates the blocking operation
// run it asynchronously: some time in the near future
downloadQueue.async { () -> Void in
// Obtain the Data with the image
let imgData = try? Data(contentsOf: url!)
// Turn it into a UIImage
let image = UIImage(data: imgData!)
// Display it
photoView.image = image
}
NSManageObjectContext
can only be run in the same queue that it was created in, therefore if we create the context in the MainQueue
we may only use it in the MainQueue
. The same rule applies if the context is created in the background - that's the only place we may use it.
Core Data in the background is second