Asynchronous Networking Approaches…

How should Asynchronous Networking be handled? This is quite a common question in various places, starting from interviews to forums like Stackoverflow. Yet, this is not a question to be answered in a sentence. There are several ways with their own strengths and weaknesses. This article is a humble effort to outline them all.

NSURLConnection Approach – New approach

The most modern of the approaches would be to use sendAsynchronousRequest:queue:completionHandler:
Following is an example of the usage of the method:

[NSURLConnection sendAsynchronousRequest:request
                                       queue:[NSOperationQueue mainQueue]
                           completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
        [self doSomethingWithData:data];
    }];

This approach has the following benefits:

  1. This does away with the repetitive code that handles intermediate results
  2. Race conditions are avoided which sometimes occur if using NSURLConnection delegates, as some of the part of the delegate gets prematurely released. The block here is retained
  3. In case of multiple asynchronous request, the code for handling each of the response is cleanly separated and thus less chance of mixup

But all these good things comes with a price tag.

  • You lose some control over the operations. Imagine the scenario when you will need to cancel the download of a large chunk of data. In the above implementation, there is no way you can actually cancel the request without leaking memory.

You can probably try cancelling the NSOperation within the queue that is sent as a parameter to the method, but that does not necessarily cancel the operation. It merely marks the operation as cancelled so that when you query the isCancelled property of the operation you get back a positive. But you will have to cancel all your activities yourself based on this isCancelled flag.

  • As stated in the first beneficial point, you can not handle intermediate results.
  • With this approach when a request is made, it either fails or succeeds, and it fails even for authentication challenges.

NSURLConnection – Traditional Approach

Then there is the traditional approach where we implement the NSURLConnectionDelegate methods and initiate the request with NSURLRequest. A quick example follows:

-(IBAction)didPressConnectButton:(id)sender {
    NSURL *url = [NSURL URLWithString:@"http://someurl.com/api?parameter"];
    NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
    self.connection1 = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}

#pragma mark - NSURLConnectionDataDelegate Methods

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    self.responseData = [[NSMutableData alloc] init];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
   [self.responseData appendData:data];

}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    if ([connection isEqual: self.connection1]) {
        NSData *data = self.responseData;
        //Do something with the data
    }
}

#pragma mark - NSURLConnectionDelegate Methods

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    //Handle error scenario
}

One benefit of using the traditional approach with NSURLConnection is that you get to handle authentication challenges through delegates. Though handling authentication challenges properly might be a lengthy and difficult, but it is nonetheless possible.

Following is the delegate method which handles authentication challenge:

- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {

}

But if there are multiple requests, then in the authentication challenge handler it becomes difficult to understand for which request the authentication challenge is thrown.

A Better Approach – NSURLSession

As we discussed both of the above approaches has their pros and cons. So, Apple has come up with an approach which takes the best of both. This is the approach with NSURLSession

Block based approach

NSString *imageUrl = @"http://images.apple.com/v/watch/c/images/nav_sport_large.jpg";
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil ];

    NSURLSessionTask *downloadTask = [session downloadTaskWithURL:[NSURL URLWithString:imageUrl] completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
        UIImage *downloadedImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:location]];
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = downloadedImage;
        });
    }];

    [downloadTask resume];

Delegate based approach

 - (void) downloadImage {
    NSString *imageUrl = @"http://images.apple.com/v/watch/c/images/nav_sport_large.jpg";
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];

    NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil ];

    NSURLSessionTask *downloadTask = [session downloadTaskWithURL:[NSURL URLWithString:imageUrl]];

    [downloadTask resume];
}

-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    // use code above from completion handler
}

//For progress indication
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    NSLog(@"%f / %f", (double)totalBytesWritten,
          (double)totalBytesExpectedToWrite);
}

Finally, the best approach in my humble opinion, would be to use AFNetworking, or RESTKit. There are other third party APIs too, like MKNetworkKit etc. I have not used MKNetworkKit by Mugunth Kumar, but the other two are really good when it comes to asynchronous networking and a myriad of other related features.

With AFNetworking, the above task can be performed as:

NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];

NSURL *URL = [NSURL URLWithString:@"http://example.com/download.zip"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];

NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
    NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
    return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
    NSLog(@"File downloaded to: %@", filePath);
}];
[downloadTask resume];

AFNetworking also allows to track progress with multipart request. The following is an example of an upload task with progress indicator:

NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:@"http://example.com/upload" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
        [formData appendPartWithFileURL:[NSURL fileURLWithPath:@"file://path/to/image.jpg"] name:@"file" fileName:@"filename.jpg" mimeType:@"image/jpeg" error:nil];
    } error:nil];

AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSProgress *progress = nil;

NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithStreamedRequest:request progress:&progress completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
    if (error) {
        NSLog(@"Error: %@", error);
    } else {
        NSLog(@"%@ %@", response, responseObject);
    }
}];

[uploadTask resume];
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s