GCD
Instead of exposing raw threads to you, GCD provides the concept of a work queue. You put work on a queue using a closure and that closure in its body can in turn dispatch work onto another GCD queue. If a queue is serial it performs closures on it sequentially. If a queue is concurrent it can dispatch multiple closures at the same time. A GCD queue is designed thread safe--you can add closures to a queue from any other queue safely.
You’ll create a dispatch function execute which runs a closure on the background queue to perform a lengthy calculation and then passes the result to a closure on the main queue when it completes. The data is copied, not shared, to avoid data race conditions.
First of all, define some functions you’ll need:
func log(message: String) {
let thread = Thread.current.isMainThread ? "Main" : "Background" print("(thread) thread: (message)")
}
func addNumbers(range: Int) -> Int { log(message: "Adding numbers...") return (1...range).reduce(0, +)
}
log(message:) uses the ternary operator to checks if the current thread is either the main or the background queue and logs a message to the console accordingly.
addNumbers(range:) calculates the sum of a given range of numbers. You’ll use this
to to represent a complicated task that must be run on a background thread. To run tasks on a background queue, you first need to create a queue:
let queue = DispatchQueue(label: "queue")
Here you created a serial queue . In a serial queue, tasks execute one at a time in FIFO (first in first out) order. You can also define a concurrent queue but then you have to deal with all of the issues of concurrency which is beyond the scope of this book. Work that is dispatched from a specific serial queue doesn’t need about simultaneous interference from another closure on the same serial queue. (Concurrent queues and sharing common data between queues is another story and does require consideration.)
Next create a method like so:
// 1
func execute<T>(backgroundWork: @escaping () -> T, mainWork: @escaping (T) -> ()) {
// 2 queue.async {
let result = backgroundWork()
// 3
DispatchQueue.main.async { mainWork(result)
}
}
}
There’s quite a lot going on here, so take it in steps:
- You make the function generic because the backgroundWork closure returns a generic result while the mainWork closure works with that result. You mark both closures with the @escaping attribute because they escape the function: you use them asynchronously, so they get called after the function returns. Closures are non-escaping by default. Non-escaping means that when the function using the closure returns it will never be used again.
- You run the backgroundWork closure asynchronously on the serial queue previously defined and store its return value.
- You dispatch the mainWork closure asynchronously on the main queue and you use the backgroundWork closure’s result as its argument.
You can’t run asynchronous code in a playground out of the box; you must enable the playground’s asynchronous mode first:
PlaygroundPage.current.needsIndefiniteExecution = true
You’ll also need to import the PlaygroundSupport framework to work with the
PlaygroundPage class.
Time to see how your brand new operator works:
execute(backgroundWork: { addNumbers(range: 100) },
mainWork: { log(message: "The sum is ($0)") })
Here, you add the numbers on the background thread and print the result to the console on the main thread. You use the underscore to discard the operator’s result. The operator outputs the following:
Background thread: Adding numbers... Main thread: The sum is 5050
Now that you know how GCD works, it’s time to see how to deal with reference cycles for asynchronous code.