Today I showed a couple of people at work a technique I use to do asynchronous URL loading in iOS, and the response on Twitter was great, so I’ve written it up for everybody. If you’re used to using ASIHTTPRequest
or rolling your own NSURLConnection
delegates, hopefully this method will be a breath of fresh air.
The Problem: When you want to load something from the Internet, you don’t want to block your UI—especially when iOS might just kill your app for doing so—but writing delegate code is a pain. You have to remember which delegate methods get called in what order, to set yourself as the delegate (can’t tell you how many times that’s tripped me up), and handling multiple simultaneous connections with one delegate is… tricky, at best.
The Solution: Use Grand Central Dispatch. Maybe I just love GCD too much and this is me seeing everything as a nail, but let’s look at the following code for loading a URL:
[sourcecode language=”objc”]- (void)loadAwesomeURL
{
NSString *awesomeURI = @"http://www.awesomeexample.com/?output=JSON";
NSURL *awesomeURL = [NSURL URLWithString:awesomeURI];
NSURLRequest *awesomeRequest = [NSURLRequest requestWithURL:awesomeURL];
NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:awesomeRequest
delegate:self];
[theConnection start];
}
– (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[myMutableData appendData:data];
}
– (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[self processTheAwesomeness];
}[/sourcecode]
That sucks. Three methods, and I didn’t even do any error handling! There has to be a better way. NSURLConnection
offers a synchronous method, but everybody knows you don’t use it… so let’s do exactly that. But since we want to make this asynchronous, we’ll use Grand Central Dispatch to wrap it in a dispatch_async()
call:
[sourcecode language=”objc”]- (void)loadAwesomeURL
{
NSString *awesomeURI = @"http://www.awesomeexample.com/?output=JSON";
NSURL *awesomeURL = [NSURL URLWithString:awesomeURI];
NSURLRequest *awesomeRequest = [NSURLRequest requestWithURL:awesomeURL];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
NSURLResponse *response = nil;
NSError *error = nil;
NSData *receivedData = [NSURLConnection sendSynchronousRequest:awesomeRequest
returningResponse:&response
error:&error];
[self processTheAwesomeness];
});
}[/sourcecode]
We can easily do error checking after the NSURLConnection
call; simply check to see if receivedData
is nil
, cast response
to an NSHTTPURLRequest
and check its statusCode
property, and if all else fails, check out error
.
Note: I’ve received a fair amount of negative feedback on this article on Twitter, Reddit, and in the comments, and I feel like I ought to make a few points clear:
- This is not the last networking solution you’ll ever need. Among other things, this does not support:
- Canceling the connection
- Running code when the connection is half-done
- Streaming data to a file for large downloads
- This is a quick example. It’s mainly designed to illustrate
dispatch_async()
as a wrapper for synchronous APIs. - It isn’t good for multiple connections. You’ll want a custom dispatch queue for that.
- It doesn’t run on the main thread. If you’re updating your UI, you’ll need to do that on the main thread.
I’d like to point out that if your processTheAwesomeness method modifies the UI in any way you need to call it on the main thread.
You also need to make any @properties atomic if you’re accessing them in the background.
A smarter way to do it would be to process using local ivars in the background block and then dispatch_async again on the main thread doing your assignments and UI manipulation in this second, nested block.
I believe you can modify the UI from a background thread if you set the view up correctly. There’s a checkbox in IB:
http://d.pr/iPKy
not really. the checkbox is there but still much of the UI is main thread only. other APIs need to be called on the main thread too.
that’s one of the things I don’t like about cocoa – having blocks is awesome but the usage is heavily limited by that mainthread-only stuffe.
I highly recommend against this approach. The biggest problem with synchronous networking is that you block the thread in which you start the request. And, you are also unable to cancel the request if say, your user navigates away from the screen for which the request was sent. The method you have here is good for demos and getting a proof of concept down, but not in shipping code.
I highly recommend making an NSURLConnection wrapper (that is the connection delegate) and just make new instances of that. It’s not much more work, and is much safer, and lower weight in terms of memory and processor usage.
Pat, properties are atomic by default. You have to specify “nonatomic” if you want that.
His point is that nonatomic properties have less overhead. If you don’t care about that (as, presumably, you don’t, if you don’t make all of your properties nonatomic by default) then you have nothing to worry about.
I highly recommend against this approach.
It doesn’t any of the edge cases that NSURLConnection does: redirects sent by the server, presenting credentials, handling iOS backgrounding. You’d be much better off using GTMHTTPFetcher from http://code.google.com/p/gtm-http-fetcher/ or
ASIHTTPRequest http://allseeing-i.com/
I agree with Saul Mora and DavidPhillipOster. NSURLConnection already handles all of the edge cases and does it cleanly. I agree that it is sometimes a PITA to write so many delegate methods, but NSURLConnection is your best bet, especially considering the sweeping changes in the iOS5 SDK.
They did a good job cleaning up the protocols attached to NSURLConnection. Not to mention that if you go with a 3rd party like ASI, you will have to exclude the whole ASI library from ARC if you’re using ARC.
Another problem with this approach is that, since sendSynchronousRequest:returningResponse:error: is blocking, your dispatch_async call with *always* create a new thread (unlike NSURLConnection, which may just use the run loop, and certainly won’t make a new thread for every single connection you have when there are many). See here (http://stackoverflow.com/questions/6538744/objective-c-dispatch-sync-vs-dispatch-async-on-main-queue/6542717#6542717), and here (http://www.mikeash.com/pyblog/friday-qa-2009-09-25-gcd-practicum.html) to see what I’m talking about.
If you really want to use blocks with NSURLConnection, it’s much easier, safer, and more readable to just write a category that wraps the boilerplate methods. Here’s one I wrote: https://gist.github.com/1095130. Be careful of memory leaks from circular references in the blocks, though.
will*
Sorry, this is actually a more relevant category: https://gist.github.com/1095187.
Thanks for the Grand Central Dispatch “in-a-nutshell” example! Don’t let negative feedback bog you down, you made it to the programming reddit page, no easy feat.
For Block-based async network callbacks, checkout Google’s HTTP Fetcher:
http://code.google.com/p/gtm-http-fetcher/