Demo: Async UIImage loading using NSOperation
Today we’re going to delve into using the awesome NSOperationQueue method for asynchronous downloads. You’re going to want to sit down for this, because it’s going to blow your mind. You can follow along with the demo project on Github, or build your own step by step.
For my example, we’re going to build a Master-Detail XCode 4 project that displays the top 50 greatest punk rock albums of all time, as listed by Kerrang! magazine. Feel free to replace the JSON payload with your own genre of music or set of images and data as long as they don’t suck.
Step 1: Grab that cover art.
(It’s okay. I already did it for you.)
If you’re rolling your own rather than using the demo project, be sure to put these images somewhere remote, rather than on your local device. Your NSOperations are going to download the images from a remote location and save them as local files.
Step 2: Build those UIViewControllers.
You’re going to want a master view, and a detail view. The master view should be the list of all the punk albums. The detail view should display an individual album’s details, such as larger cover art, title, and band.
Here’s a typical scenario: You have an API that has some data that you query, giving you a list of items with associated images. You’re most likely getting this payload back as JSON, and you’re most likely parsing that JSON into an NSDictionary using a JSON parsing framework. You’re then most likely using that NSDictionary as a reference for your UITableViewController.
The key element here is speed. You want the UITableViewController that powers the list to be super slick and speedy. And if you’re stuck waiting on each individual image to download as you scroll, you’re going to be one unhappy camper. This is where NSOperation comes in. Think of an NSOperation as one of those tiny birds in Angry Birds. They’re speedy, small, and powerful. Each NSOperation is basically a custom object that handles a small chunk of activity, such as downloading a file, or parsing a JSON payload into a Core Data object.
At the end of this step, you should have your UIViewControllers set up and ready to go, and you should have a method that you’ll be adding triggers to for the creation and firing of several NSOperations.
Step 3: Add NSOperations and an NSOperationQueue
NSOperations are handled by the NSOperationQueue. Much like NSUserDefaults, you have a standard default queue that you can use for basic stuff. But I recommend creating your own to handle your custom operations. It’s super easy and straightforward.
Here’s a basic example:
MyCustomOperation *operation = [[[MyCustomOperation alloc] init] autorelease]; operation.url = @"http://www.example.com/my_large_image.jpg"; NSOperationQueue *queue = [NSOperationQueue new]; [queue addOperation:operation];
Here’s an example from the demo project that demonstrates downloading a JSON payload, then parsing that payload into individual NSOperation objects that are then added to an NSOperationQueue. The NSOperationQueue handles all the issues associated with threading, timing, and to a certain (customizable) extent, performance.
Easy peasy right? Each custom NSOperation you create should handle its heavy lifting in the main void. Check out this example to see an NSOperation that handles downloading an image and then saving it to file.
Step 4: Add an observer so you know when your NSOperationQueue is finished
Ideally in viewDidLoad, you’re going to want to add yourself as an observer for the queue. Each time an NSOperation completes, the observeValueForKeyPath method will get called. This is where you’ll be able to handle each individual operation’s completion as well as the entire queue.
Here’s how to do that:
[self.myCustomQueue addObserver:self forKeyPath:@"operations" options:0 context:NULL];
And then you’ll want to put some logic in that method to handle completion. Depending on the requirements of your app, you may care about each time an NSOperation is fired, or just when your queue’s operationCount property becomes zero. In the demo app, we care about both: we want our table to refresh each time an image is downloaded so we don’t have to wait for all of them to download, and we want our spinner to stop once all images have downloaded. We do that like this:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context {
if ((object == self.imageQueue) && ([keyPath isEqualToString:@"operations"])) {
if (self.imageQueue.operationCount == 0) {
// We're done downloading images
self.isLoading = NO;
}
// We want to refresh no matter where we are in the queue, so our covers load quickly!
[self performSelectorOnMainThread:@selector(refresh) withObject:nil waitUntilDone:NO];
}
}
And that’s pretty much it! Notice how smooth the user interaction is? That’s all thanks to NSOperations. They’re acting like tiny little Angry Birds, handling the heavy lifting that would normally cause your thread to stall, your users to get frustrated, and your app to suck. Suckage be gone!
Posted from Houston, Texas, United States.
-
Categories
-
Meta




