NSURLConnection vs. NSData + GCD

Matt Wilding picture Matt Wilding · Sep 14, 2011 · Viewed 7.5k times · Source

NSData has always had a very convenient method called +dataWithContentsOfURL:options:error:. While convenient, it also blocks execution of the current thread, which meant it was basically useless for production code (Ignoring NSOperation). I used this method so infrequently, I completely forgot that it existed. Until recently.

The way I've been grabbing data from the tubes is the standard NSURLConnectionDelegate approach: Write a download class that handles the various NSURLConnectionDelegate methods, gradually build up some data, handle errors, etc. I'll usually make this generic enough to be reused for as many requests as possible.

Say my typical downloader class runs somewhere in the ballpark of 100 lines. That's 100 lines to do asynchronously what NSData can do synchronously in one line. For more complexity, that downloader class needs a delegate protocol of its own to communicate completion and errors to its owner, and the owner needs to implement that protocol in some fashion.

Now, enter Grand Central Dispatch, and I can do something as fantastically simple as:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {

    NSData* data = [NSData dataWithContentsOfURL:someURL];
    // Process data, also async...

    dispatch_async(dispatch_get_main_queue(), ^(void) {
        // Back to the main thread for UI updates, etc.
    });
});

And I can throw that sucker in anywhere I want, right in-line. No need for a download class, no need to handle connection delegate methods: Easy async data in just a few lines. The disparity between this approach and my pre-GCD approach is of a magnitude great enough to trigger the Too Good to be True Alarm.

Thus, my question: Are there any caveats to using NSData + GCD for simple data download tasks instead of NSURLConnection (Assuming I don't care about things like download progress)?

Answer

AliSoftware picture AliSoftware · Sep 14, 2011

You are losing a lot of functionality here:

  • Can't follow the download progression
  • Can't cancel the download
  • Can't manage the possible authentication process
  • You can't handle errors easily, which is really important especially in mobile development like on iPhone of course (because you often lose your network in real conditions, so it is very important to track such network error cases when developing for iOS)

and there's probably more I guess.


The right approach for that is to create a class than manages the download.

See my own OHURLLoader class for example, which is simple and I made the API to be easy to use with blocks:

NSURL* url = ...
NSURLRequest* req = [NSURLRequest requestWithURL:url];

OHURLLoader* loader = [OHURLLoader URLLoaderWithRequest:req];
[loader startRequestWithCompletion:^(NSData* receivedData, NSInteger httpStatusCode) {
    NSLog(@"Download of %@ done (statusCode:%d)",url,httpStatusCode);
    if (httpStatusCode == 200) {
        NSLog(%@"Received string: %@", loader.receivedString); // receivedString is a commodity getter that interpret receivedData using the TextEncoding specified in the HTTP response
    } else {
        NSLog(@"HTTP Status code: %d",httpStatusCode); // Log unexpected status code
    }
} errorHandler:^(NSError *error) {
    NSLog(@"Error while downloading %@: %@",url,error);
}];

See the README file and sample project on github for more info.

This way:

  • you still rely on the asynchronous methods provided by NSURLConnection (and as the Apple's documentation says about Concurrency Programming if an API already exists to make asynchronous tasks, use it instead of relying on another threading technology if possible)
  • you keep advantages of NSURLConnection (error handlings, etc)
  • but you also have the advantages of the blocks syntax that makes your code more readable than when using delegate methods