Multithreading and Grand Central Dispatch on iOS for Beginners Tutorial

Have you ever written an app where you tried to do something, and there was a long pause while the UI was unresponsive? This is usually a sign that your app needs multithreading! In this tutorial, you’ll get hands on experience with the core multithreading API available on iOS: Grand Central Dispatch. You’ll take an […] By Ray Wenderlich.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

Downloading Asynchronously

Let’s start by replacing the slowest operation with asynchronous calls – the downloading of the files.

It’s actually not that difficult to do this with the built-in Apple classes – NSURLRequest and NSURLConnection – but I’m a fan of some wrapper classes that make this even easier – ASIHTTPRequest.

We’re going to use this to asynchronously download the files, so let’s add it to your project.

If you don’t have ASIHTTPRequest already, first download it. Once you have it downloaded, right click your ImageGrabber project entry in groups and files, select New Group, and name the new group ASIHTTPRequest. Then drag all of the files from the ASIHTTPRequest\Classes directory (ASIAuthenticationDialog.h and several others, but IMPORTANT! don’t add the subfolders such as ASIWebPageRequest, CloudFiles, S3, and Tests.) into the new ASIHTTPRequest group. Make sure “Copy items into destination group’s folder (if needed)” is selected, and click Finish.

Also repeat this for the two files in ASIHTTPRequest\External\Reachability, as these are dependencies of the project.

The last step to add ASIHTTPRequest is you need to link your project against a few required frameworks. To do this, click on your ImageGrabber project entry in Groups & Files, click the PromoTest target, choose the Build Phases tab, and expand the Link Binary with Libraries section. Click the plus button in this section, and choose CFNetwork.framework. Then repeat this for SystemConfiguration.framework and MobileCoreServices.framework.

Now it’s time to replace the old bad synchronous code with the new good asynchronouse code!

Open up ImageManager.m and make the following changes:

// Add to top of file
#import "ASIHTTPRequest.h"

// Replace retrieveZip with the following
- (void)retrieveZip:(NSURL *)sourceURL {
    
    NSLog(@"Getting %@...", sourceURL);
    
    __block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:sourceURL];
    [request setCompletionBlock:^{
        NSLog(@"Zip file downloaded.");
        NSData *data = [request responseData];
        [self processZip:data sourceURL:sourceURL];        
    }];
    [request setFailedBlock:^{
        NSError *error = [request error];
        NSLog(@"Error downloading zip file: %@", error.localizedDescription);
    }];
    [request startAsynchronous];    
}

This sets up an ASIHTTPRequest with a given URL. It sets up a block of code to run when the request finishes, and one to run if the requst fails for some reason.

Then it calls startAsynchronous. This method returns immediately so the main thread can continue going about its business such as animating the UI and responding to user input. In the meantime, the OS will automatically run the code to download the zip file on a background thread, and call one of the callback blocks when it completes or fails!

Similarly, switch to ImageInfo.m and make similar changes there:

// Add to top of file
#import "ASIHTTPRequest.h"

// Replace getImage with the following
- (void)getImage {
  
    NSLog(@"Getting %@...", sourceURL);
    
    __block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:sourceURL];
    [request setCompletionBlock:^{
        NSLog(@"Image downloaded.");
        NSData *data = [request responseData];
        image = [[UIImage alloc] initWithData:data];
    }];
    [request setFailedBlock:^{
        NSError *error = [request error];
        NSLog(@"Error downloading image: %@", error.localizedDescription);
    }];
    [request startAsynchronous];    
}

This is pretty much the same as the other code – it runs the download in the background, and when it completes sets the image instance variable to the result.

Let’s see if it works! Compile and run and tap “Grab!” and viola – it quickly switches to the detail tab rather than having a super-long pause! However there’s one major problem:

Update a row in a UITableView when image loads

The images don’t show up in the table view after they’re downloaded! You can get them to show up by scrolling the table up and down (which works because the data for the row is reloaded after it goes offscreen), but that is kind of a hack. How can we fix this?

Introducing NSNotifications

One easy way to send updates from one part of your code to another is Apple’s built-in NSNotification system.

It’s quite simple. You get the NSNotificationCenter singleton (via [NSNotificationCenter defaultCenter]) and:

  1. If you have an update you want to send, you call postNotificationName. You just give it a unique string you make up (such as “com.razeware.imagegrabber.imageupdated”) and an object (such as the ImageInfo that just finished downloading its image).
  2. If you want to find out when this update happens, you call addObserver:selector:name:object. In our case the ImageListViewController will want to know when this happens so it can reload the appropriate table view cell. A good spot to put this is in viewDidLoad.
  3. Don’t forget to call removeObserver:name:object when the view gets unloaded. Otherwise, the notification system might try to call a method on an unloaded view (or worse an unallocated object), which would be a bad thing!

So let’s try this out. Open up ImageInfo.m and make the following modification:

// Add inside getImage, right after image = [[UIImage alloc] initWithData:data];
[[NSNotificationCenter defaultCenter] postNotificationName:@"com.razeware.imagegrabber.imageupdated" object:self];

So once the image is downloaded, we post a notification and pass in this object that just got updated (self).

Next switch to ImageListViewController.m and make the following modifications:

// At end of viewDidLoad
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(imageUpdated:) name:@"com.razeware.imagegrabber.imageupdated" object:nil];

// At end of viewDidUnload
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"com.razeware.imagegrabber.imageupdated" object:nil];

// Add new method
- (void)imageUpdated:(NSNotification *)notif {
    
    ImageInfo * info = [notif object];
    int row = [imageInfos indexOfObject:info];
    NSIndexPath * indexPath = [NSIndexPath indexPathForRow:row inSection:0];
    
    NSLog(@"Image for row %d updated!", row);
    
    [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
        
}

This registers for the notification in viewDidUnload, basically saying “hey call imageUpdated when this notifiation arrives!” It also deregisters appropriately in viewDidUnload.

The imageUpdated callback looks inside the array of imageInfos for the passed in object. Once it finds it, it gets the indexPath of that row, and tells the table view to reload that row.

Compile and run, and now you’ll see the images pop in as they’re downloaded!

Asynchronous image loading

Grand Central Dispatch and Dispatch Queues, Oh My!

There’s still a problem with our app. If you tap the “Grab!” button and keep scrolling up and down continuously as soon as the detail view loads, after the zip file downloads you’ll see the entire UI freeze as it’s saving and unzipping the zip file.

This is because the completion block in ASIHTTPRequest gets called in the main thread, and we called the code to process the zip file within the main thread:

[request setCompletionBlock:^{
    NSLog(@"Zip file downloaded.");
    NSData *data = [request responseData];
    [self processZip:data sourceURL:sourceURL]; // Ack - heavy work on main thread!
}];

So how can we run this heavy work in the background?

Well, iOS 3.2 introduced a very simple (and very efficient) way to do this via the Grand Central Dispatch system. Basically, whenever you want to run something in the background, you just call dispatch_async and pass in some code to run.

Grand Central Dispatch will handle all of the details for you – it will create a new thread if it needs to, or reuse an old one if one is available.

When you call dispatch_async, you pass in a dispatch queue. You can think of this as an list that stores all the blocks that you pass in, first in first out.

You can make your own dispatch queues (via dispatch_create), or you can get a special dispatch queue for the main thread (via dispatch_get_main_queue). We’ll be making a background queue called “backgroundQueue” that we’ll use to run processing tasks in the background, like parsing XML or saving/unzipping zip files.

Contributors

Over 300 content creators. Join our team.