Swift, iOS, Threading dispatch_async

We’re going to use dispatch_async() twice: once to push some code to a background thread, then once more to push code back to the main thread. This allows us to do any heavy lifting away from the user interface where we don’t block things, but then update the user interface safely on the main thread – which is the only place it can be safely updated.

When you call dispatch_async(), you must tell it where you want the code to run. GCD works with a system of queues, which are much like a real-world queue: they are First In, First Out (FIFO) blocks of code. What this means is that your GCD calls don’t create threads to run in, they just get assigned to one of the existing threads for GCD to manage.

GCD creates for you a number of queues, and places tasks in those queues depending on how important you say they are. All are FIFO, meaning that each block of code will be taken off the queue in the order they were put in, but more than one code block can be executed at the same time so the finish order isn’t guaranteed.

“How important” some code is depends on something called “quality of service”, or QoS, which decides what level of service this code should be given. Obviously at the top of this is the main queue, which runs on your main thread, and should be used to schedule any work that must update the user interface immediately even when that means blocking your program from doing anything else. But there are four background queues that you can use, each of which has their own QoS level set:

  1. User Interactive: this is the highest priority background thread, and should be used when you want a background thread to do work that is important to keep your user interface working. This priority will ask the system to dedicate nearly all available CPU time to you to get the job done as quickly as possible.
  2. User Initiated: this should be used to execute tasks requested by the user that they are now waiting for in order to continue using your app. It’s not as important as user interactive work – i.e., if the user taps on buttons to do other stuff, that could should be executed first – but it is important because you’re keeping the user waiting.
  3. The Utility queue: this should be used for long-running tasks that the user is aware of, but not necessarily desperate for now. If the user has requested something and can happily leave it running while they do something else with your app, you should use Utility.
  4. The Background queue: this is for long-running tasks that the user isn’t actively aware of, or at least doesn’t care about its progress or when it completes.

Those QoS queues affect the way the system prioritises your work: User Interactive and User Initiated tasks will be executed as quickly as possible regardless of their effect on battery life, Utility tasks will be executed with a view to keeping power efficiency as high as possible without sacrificing too much performance, whereas Background tasks will be executed with power efficiency as its priority.

GCD automatically balances work so that higher priority queues are given more time than lower priority ones, even if that means temporarily delaying a background task because a user interactive task just came in.

Enough talking, time for some action: we’re going to use dispatch_async() to make all our loading code run in the background queue with User Initiated quality of service. It’s actually only two lines of code different:

dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) { [unowned self] in

…before the code you want to run in the background, then a closing brace at the end.

dispatch_async() takes one parameter, then a closure to execute asynchronously. The parameter it takes is which queue you want to use, and we’re going to use one of two functions: dispatch_get_global_queue() asks for a queue with a particular quality of service setting, and dispatch_get_main_queue() will use the main queue.

The dispatch_get_global_queue() also takes parameters, but the second one is always 0 – Apple has left it there in case they want to make changes in the future, but until you hear otherwise just put a 0 in there. The first parameter is the name of the queue you want to use, and in our code that’s QOS_CLASS_USER_INITIATED: we want our code to execute quickly because we know the user is waiting for it.

Because dispatch_async() uses closures, we start with [unowned self] in to avoid strong reference cycles, but otherwise our loading code is the same as before. To help you place it correctly, here’s how the loading code should look:

dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) { [unowned self] in
    if let url = NSURL(string: urlString) {
        if let data = try? NSData(contentsOfURL: url, options: []) {
            let json = JSON(data: data)

            if json["metadata"]["responseInfo"]["status"].intValue == 200 {
                self.parseJSON(json)
            } else {
                self.showError()
            }
        } else {
            self.showError()
        }
    } else {
        self.showError()
    }
}

Note that because our code is now inside a closure, we need to prefix our method calls with self. otherwise Swift complains.

If you want to try the other QoS queues, you could also use QOS_CLASS_USER_INTERACTIVE, QOS_CLASS_UTILITY orQOS_CLASS_BACKGROUND.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s