Sunday, 21 December 2014

Networking with NSURLSession: Part 1

This post is part of a series called Working with NSURLSession.
Networking with NSURLSession: Part 2
From a developer's perspective, one of the more significant changes in iOS 7, and OS X Mavericks for that matter, is the introduction of NSURLSession. Even though NSURLSession may seem daunting at first glance, it's important that you understand what it is, how it relates to NSURLConnection, and what the differences are. In this series, I will take you through the fundamentals of NSURLSession so you can take advantage of this new technology in your own applications.

The first question you may be asking yourself is why Apple found it necessary to introduce NSURLSession while we are perfectly happy with NSURLConnection. The real question is whether you are happy with NSURLConnection. Do you remember that time you were cursing and throwing things at NSURLConnection? Most Cocoa developers have been in that place, which is why Apple decided to go back to the drawing board and create a more elegant solution, better suited for the modern web.
Even though NSURLSession and NSURLConnection have a lot in common in terms of how they work, at a fundamental level, they are quite different. Apple created NSURLSession to resemble the general concepts of NSURLConnection, but you will learn in the course of this series that NSURLSession is modern, easier to use, and built with mobile in mind.
Before I discuss the differences between NSURLSession and NSURLConnection, it's a good idea to first take a closer look at what NSURLSession is. Despite its name, NSURLSession isn't just another class you can use in an iOS or OS X application. NSURLSession is first and foremost a technology just like NSURLConnection is.
NSURLSession and NSURLConnection both provide an API for interacting with various protocols, such as HTTP and HTTPS. The session object, an instance of the NSURLSession class, is what manages this interaction. It is a highly configurable container with an elegant API that allows for fine-grained control. It offers features that are absent in NSURLConnection. What's more, with NSURLSession, you can accomplish tasks that are simply not possible with NSURLConnection, such as implementing private browsing.
The basic unit of work when working with NSURLSession is the task, an instance of NSURLSessionTask. There are three types of tasks, data tasks, upload tasks, and download tasks.
  • You'll most often use data tasks, which are instances of NSURLSessionDataTask. Data tasks are used for requesting data from a server, such as JSON data. The principal difference with upload and download tasks is that they return data directly to your application instead of going through the file system. The data is only stored in memory.
  • As the name implies, upload tasks are used to upload data to a remote destination. The NSURLSessionUploadTask is a subclass of NSURLSessionDataTask and behaves in a similar fashion. One of the key differences with a regular data task is that upload tasks can be used in a session created with a background session configuration.
  • Download tasks, instances of NSURLSessionDownloadTask, inherit directly from NSURLSessionTask. The most significant difference with data tasks is that a download task writes its response directly to a temporary file. This is quite different from a regular data task that stores the response in memory. It is possible to cancel a download task and resume it at a later point.
As you can imagine, asynchronicity is a key concept in NSURLSession. The NSURLSession API returns data by invoking a completion handler or through the session's delegate. The API of NSURLSession was designed with flexibility in mind as you'll notice a bit later in this tutorial.
As I mentioned earlier, NSURLSession is both a technology and a class that you'll be working with. The NSURLSession API houses a number of classes, but NSURLSession is the key component sending requests and receiving responses. The configuration of the session object, however, is handled by an instance of the NSURLSessionConfiguration class. The NSURLSessionTask class and its three concrete subclasses are the workers and are always used in conjunction with a session as it is the session that creates the task objects.
Both NSURLSession and NSURLConnection rely heavily on the delegation pattern. The NSURLSessionDelegate protocol declares a handful of delegate methods for handling events at the session-level. In addition, the NSURLSessionTask class and subclasses each declare a delegate protocol for handling task-level events.
The NSURLSession API builds on top of classes that you're already familiar with, such as NSURL, NSURLRequest, and NSURLResponse.
How does NSURLSession differ from NSURLConnection? This is an important question, because NSURLConnection is not being deprecated by Apple. You can still use NSURLConnection in your projects. Why should you use NSURLSession?
The first thing to understand is that the NSURLSession instance is the object that manages the request and response. This is similar to how NSURLConnection works, but the key difference is that the configuration of the request is handled by the session object, which is a long lived object. This is done through the NSURLSessionConfiguration class. Not only does it provide the NSURLSession API fine-grained configuration through the NSURLSessionConfiguration class, it encourages the separation of data (request body) from metadata. The NSURLSessionDownloadTask illustrates this well by directly writing the response to the file system.
Authentication is easier and handled more elegantly by NSURLSession. The NSURLSession API handles authentication on a connection basis instead of on a request basis, like NSURLConnection does. The NSURLSession API also makes it more convenient to provide HTTP options and each session can have a separate storage container depending on how you configure the session.
In the introduction, I told you that NSURLSession provides a modern interface, which integrates gracefully with iOS 7. One example of this integration is NSURLSession's out-of-process uploads and downloads. NSURLSession is optimized to preserve battery life, supports pausing, canceling, and resuming of tasks as well as UIKit's multitasking API. What is not to love about NSURLSession?
A new API is best learned by practice so it's time to fire up Xcode and get our feet wet. Launch Xcode 5, create a new project by selecting New > Project... from the File menu, and select the Single View Application template from the list of iOS application templates.
Create a new project in Xcode 5.
Give your project a name, tell Xcode where you'd like to save it, and hit Create. There's no need to put the project under source control.
Configure the project.
When working with NSURLSession, it is important to understand that the session object, an instance of NSURLSession, is the star player. It handles the requests and responses, configures the requests, manages session storage and state, etc. Creating a session can be done several ways. The quickest way to get started is to use NSURLSession's sharedSession class method as shown below.
1
2
3
4
5
- (void)viewDidLoad {
    [super viewDidLoad];
 
    NSURLSession *session = [NSURLSession sharedSession];
}
Create a session object in the view controller's viewDidLoad method as shown above. The session object we created is fine for our example, but you probably want a bit more flexibility in most cases. The session object we just created uses the global NSURLCache, NSHTTPCookieStorage, and NSURLCredentialStorage. This means that it works pretty similar to a default implementation of NSURLConnection.
To put the session object to use, let's query the iTunes Store Search API and search for software made by Apple. The iTunes Store Search API is easy to use and requires no authentication, which makes it ideal for our example.
To query the search API, we need to send a request to https://itunes.apple.com/search and pass a few parameters. As we saw earlier, when using the NSURLSession API, a request is represented by a task. To query the search API, all we need is a data task, an instance of the NSURLSessionDataTask class. Take a look at the updated viewDidLoad implementation shown below.
1
2
3
4
5
6
7
8
9
- (void)viewDidLoad {
    [super viewDidLoad];
 
    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:@"https://itunes.apple.com/search?term=apple&media=software"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
        NSLog(@"%@", json);
    }];
}
There are a number of methods available to create a task, but the key concept to understand is that the session object does the actual creation and configuration of the task. In this example, we invoke dataTaskWithURL:completionHandler: and pass it an instance of NSURL as well as a completion handler. The completion handler accepts three arguments, the raw data of the response (NSData), the response object (NSURLResponse), and an error object (NSError). If the request is successful, the error object is nil. Because we know the request returns a JSON response, we create a Foundation object from the data object that we've received and log the output to the console.
It is important to understand that the error object passed to the completion handler is only populated, not nil, if the request failed or encountered an error. In other words, if the request returned a 404 response, the request did succeed as far as the sessions is concerned. The error object will then be nil. This is an important concept to grasp when working with NSURLSession and NSURLConnection for that matter.
Build the project and run the application in the iOS Simulator or on a physical device, and inspect Xcode's Console. Nothing is printed to the console. What went wrong? As I mentioned earlier, the NSURLSession API supports pausing, canceling, and resuming of tasks or requests. This behavior is similar to that of NSOperation and it may remind you of the AFNetworking library. A task has a state property that indicates whether the task is running (NSURLSessionTaskStateRunning), suspended (NSURLSessionTaskStateSuspended), canceling (NSURLSessionTaskStateCanceling), or completed (NSURLSessionTaskStateCompleted). When a session object creates a task, the task starts its life in the suspended state. To start the task, we need to tell it to resume by calling resume on the task. Update the viewDidLoad method as shown below, run the application one more time, and inspect the output in the console. Mission accomplished.
01
02
03
04
05
06
07
08
09
10
11
- (void)viewDidLoad {
    [super viewDidLoad];
 
    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:@"https://itunes.apple.com/search?term=apple&media=software"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
        NSLog(@"%@", json);
    }];
 
    [dataTask resume];
}
In the previous example, we made use of a completion handler to process the response we received from the request. It's also possible to achieve the same result by implementing the task delegate protocol(s). Let's see what it takes to download an image by leveraging NSURLSession and the NSURLSessionDownloadTask.
Open MTViewController.h and create two outlets as shown below. We'll use the first outlet, an instance of UIImageView, to display the downloaded image to the user. The second outlet, an instance of UIProgressView, will show the progress of the download task.
1
2
3
4
5
6
7
8
#import <UIKit/UIKit.h>
 
@interface MTViewController : UIViewController
 
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property (weak, nonatomic) IBOutlet UIProgressView *progressView;
 
@end
Open the project's main storyboard (Main.storyboard), drag a UIImageView instance to the view controller's view, and connect the view controller's outlet that we just created in the view controller's header file. Repeat this process for the progress view.
Configure the application's user interface.
In this example, we won't make use of the sharedSession class method, because we need to configure the session object that we'll use to make the request. Update the implementation of viewDidLoad as shown below.
1
2
3
4
5
6
7
8
- (void)viewDidLoad {
    [super viewDidLoad];
 
    NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:nil];
    NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:[NSURL URLWithString:@"http://cdn.tutsplus.com/mobile/uploads/2013/12/sample.jpg"]];
    [downloadTask resume];
}
To prevent any compiler warning from popping up, make sure to conform the MTViewController class to the NSURLSessionDelegate and NSURLSessionDownloadDelegate protocols as shown below.
1
2
3
4
5
#import "MTViewController.h"
 
@interface MTViewController () <NSURLSessionDelegate, NSURLSessionDownloadDelegate>
 
@end
In viewDidLoad, we create an instance of the NSURLSessionConfiguration class by invoking the defaultSessionConfiguration class method. As stated in the documentation, by using the default session configuration the session will behave much like an instance of NSURLConnection in its default configuration, which is fine for our example.
In this example, we create a NSURLSession instance by invoking the sessionWithConfiguration:delegate:delegateQueue: class method and pass the sessionConfiguration object we created a moment ago. We set the view controller as the session delegate and pass nil as the third argument. You can ignore the third argument for now. The main difference with the previous example is that we set the session's delegate to the view controller.
To download the image, we need to create a download task. We do this by calling downloadTaskWithURL: on the session object, passing an instance of NSURL, and calling resume on the download task. We could have made use of a completion handler like we did earlier, but I want to show you the possibilities of using a delegate instead.
Advertisement
To make all this work, we need to implement the three delegate methods of the NSURLSessionDownloadDelegate protocol, URLSession:downloadTask:didFinishDownloadingToURL:, URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:, and URLSession:downloadTask:downloadTask didWriteData:totalBytesWritten:totalBytesExpectedToWrite:. The implementation of each method is quite easy. It's important to note that we need update the user interface on the main thread using GCD (Grand Central Dispatch). By passing nil as the third argument of sessionWithConfiguration:delegate:delegateQueue:, the operating system created a background queue for us. This is fine, but it also means that we need to be aware that the delegate methods are invoked on a background thread instead of the main thread. Build the project and run the application to see the result of our hard work.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
    NSData *data = [NSData dataWithContentsOfURL:location];
 
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.progressView setHidden:YES];
        [self.imageView setImage:[UIImage imageWithData:data]];
    });
}
 
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes {
 
}
 
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
    float progress = (double)totalBytesWritten / (double)totalBytesExpectedToWrite;
 
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.progressView setProgress:progress];
    });
}
With these two examples, you should have a basic understanding of the fundamentals of the NSURLSession API, how it compares to NSURLConnection, and what its advantages are. In the next part of this series, we will look at more advanced features of NSURLSession.

No comments:

Post a Comment