Kyle Banks

Asynchronous Image Downloading in iOS and Mac OS with Objective-C

Written by @kylewbanks on Oct 14, 2013.

See the update at the bottom for the Mac OS implementation.

A common issue I have seen with various code snippets around the internet is that developers are unwittingly downloading images synchronously in iOS (or Mac OS for that matter) apps, causing the application to freeze as the main UI thread is blocked. This is poor practice for a number of reasons, but the main issue is that the app appears to be unresponsive while the image is downloaded. Depending on the connection of the device, this can severely harm the usability of your application.

The most common way that I have seen this done is so:

//Do NOT do this!
[UIImage imageWithData:[NSData dataWithContentsOfURL:myImageURL]];

+ (NSData *) dataWithContentsOfURL:(NSURL *)url; returns data from a URL, which is what we want, but it does it synchronously. Do not use this method for downloading remote data!

I have developed what I believe to be a better, and still simple solution (although admittedly lacking more advanced features like caching) that I use in almost all of my projects that do not require anything complicated (again, caching). This simple class handles the image download asynchronously, and uses blocks to inform you when the image download completes or fails.

Usage is simple, you just initialize the AsyncImageDownloader with a URL, and success/failure blocks. You can instruct the AsyncImageDownloader to start downloading immediately, or assign the downloader to a variable and start the download when you are ready.

UIImageView *myImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
	
[[[AsyncImageDownloader alloc] initWithMediaURL:@"http://example.com/awesome_pic.jpg" successBlock:^(UIImage *image)  {
      [myImageView setImage:image];
} failBlock:^(NSError *error) {
      NSLog(@"Failed to download image due to %@!", error);
}] startDownload];

Clone the source from GitHub, use it, change it, do whatever you like with it. If you have any suggestions for improvements, feel free to leave me a message in the comments! Keep in mind that this class is meant to be very barebones and light-weight. If you want more advanced features like caching, there are many options available to you including my personal favourite SDWebImage.

Note that at this time, the AsyncImageDownloader will not work in Mac OS. It would be very easy to port it over, and I may get around to doing so in the near future. If not, I encourage anybody to port it over and put it up somewhere for the community to access.

Update

Github user nicoschtein pushed a pull request to offer asynchronous downloading of any data type, not just images. The class works exactly the same as before, but now offers more flexibility and reuse for non-image downloads.

Update 2 - Mac OS Implementation

I have added a Mac OS version of AsyncImageDownloader to the GitHub repo. The syntax to use it is almost identical, however you replace UIImage with NSImage, like so:

NSImageView *myImageView = ...;
	
[[[AsyncImageDownloader alloc] initWithMediaURL:@"http://example.com/awesome_pic.jpg" successBlock:^(NSImage *image)  {
      [myImageView setImage:image];
} failBlock:^(NSError *error) {
      NSLog(@"Failed to download image due to %@!", error);
}] startDownload];

Update 3 - CocoaPods

I've published the AsyncImageDownloader to CocoaPods, making it even easier to integrate the library into your project. Read more about setting that up, right here.

Let me know if this post was helpful on Twitter @kylewbanks or down below, and follow me to keep up with future posts!