Tuesday 17 June 2014

Asynchronous Operations in iOS with Grand Central Dispatch

Grand Central Dispatch, or GCD for short, is a C API that makes it exceptionally easy to perform asynchronous operations in iOS. Asynchronous operations are a fundamental part of every iOS app when you want to perform long operations without freezing or blocking the user interface. You can image that if your entire app froze without warning then your users would get quite irritated.
With GCD, you can line up blocks of code in a queue for the system to execute as necessary. These blocks or operations, will be dispatched in the queue to another thread, leaving your main UI thread to continue its tasks. It’s important to know that GCD will use separate threads for it’s operations but which thread it isn’t important to you. All that matters is that your long running process in a separate thread outside the main thread where your UI and gestures are running.

GCD queues can be concurrent or serial but serial queues (where there is one queue and each item is executed one after the other in order) are easier to understand so we’ll look at those.
The more important functions you’ll need are for creating the queue:

dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
and adding blocks to the queue:

void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
 
There’s also a couple helper functions for retrieving specific queues such as:
dispatch_queue_t dispatch_get_current_queue(void);
dispatch_queue_t dispatch_get_main_queue(void);
 
The dispatch_get_current_queue function will return the current queue from which the block is dispatched and the dispatch_get_main_queue function will return the main queue where your UI is running.

The dispatch_get_main_queue function is very useful for updating the iOS app’s UI as UIKit methods are not thread safe (with a few exceptions) so any calls you make to update UI elements must always be done from the main queue.

A typical GCD call would look something like this:
// Doing something on the main thread

dispatch_queue_t myQueue = dispatch_queue_create("My Queue",NULL);
dispatch_async(myQueue, ^{
    // Perform long running process
    
    dispatch_async(dispatch_get_main_queue(), ^{
        // Update the UI
        
    });
}); 

// Continue doing other stuff on the 
// main thread while process is running.
GCD relies on block so it has a really nice, readable syntax. It becomes clear what happes in the background thread and the main thread. For example, here’s how you might load a few images:
NSArray *images = @[@"http://example.com/image1.png",
                 @"http://example.com/image2.png",
                 @"http://example.com/image3.png",
                 @"http://example.com/image4.png"];

dispatch_queue_t imageQueue = dispatch_queue_create("Image Queue",NULL);

for (NSString *urlString in images) {
    dispatch_async(imageQueue, ^{
        
        NSURL *url = [NSURL URLWithString:urlString];
        NSData *imageData = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:imageData];

        NSUInteger imageIndex = [images indexOfObject:urlString];
        UIImageView *imageVIew = (UIImageView *)[self.view viewWithTag:imageIndex];
        
        if (!imageView) return;
        
        dispatch_async(dispatch_get_main_queue(), ^{
            // Update the UI
            [imageVIew setImage:image];
        });
        
    }); 
}

// Continue doing other stuff while images load.
 
You’ll notice I check for imageView before dispatching to the main thread. This avoids the main queue dispatch if the network request took a long time and the imageView is no longer there for one reason or another. Once a block has been dispatched onto a queue, it’s not possible to stop it. The block will execute to completion and there’s nothing you can do. If you have a very long running process, such as loading a URL, you should add logic between each step to check to see if it should continue and if it’s still appropriate to finish the operation.
That’s it. Start build queues and making your UI more responsive.

Aside: Concurrency

If you want to run a single independent queued operation and you’re not concerned with other concurrent operations, you can use the global concurrent queue:
dispatch_queue_t globalConcurrentQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
This will return a concurrent queue with the given priority as outlined in the documentation:
DISPATCH_QUEUE_PRIORITY_HIGH Items dispatched to the queue will run at high priority, i.e. the queue will be scheduled for execution before any default priority or low priority queue.
DISPATCH_QUEUE_PRIORITY_DEFAULT Items dispatched to the queue will run at the default priority, i.e. the queue will be scheduled for execution after all high priority queues have been scheduled, but before any low priority queues have been scheduled.
DISPATCH_QUEUE_PRIORITY_LOW Items dispatched to the queue will run at low priority, i.e. the queue will be scheduled for execution after all default priority and high priority queues have been scheduled.
DISPATCH_QUEUE_PRIORITY_BACKGROUND Items dispatched to the queue will run at background priority, i.e. the queue will be scheduled for execution after all higher priority queues have been scheduled and the system will run items on this queue on a thread with background status as per setpriority(2) (i.e. disk I/O is throttled and the thread’s scheduling priority is set to lowest value).
from queue.h

 

No comments:

Post a Comment