Asynchronous Synchronous Requests: Effortless Networking Code

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:
    1. Canceling the connection
    2. Running code when the connection is half-done
    3. 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.