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.
Why Replace NSURLConnection
?
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.
What is NSURLSession
?
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.
Sessions are Containers
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.
Tasks
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 ofNSURLSessionDataTask
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 fromNSURLSessionTask
. 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.
Meet the Family
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.
Delegation
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.
Old Friends
The NSURLSession
API builds on top of classes that you're already familiar with, such as NSURL
, NSURLRequest
, and NSURLResponse
.
What are the differences?
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
?
Getting Your Feet Wet
Step 1: Project Setup
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.
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.
Step 2: Create a Session Object
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.
- (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
.
Step 3: Create a Data Task
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.
- (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.
- (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]; }
Downloading a Remote Resource
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
.
Step 1: Create the User Interface
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.
#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.
Step 2: Create a Download Task
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.
- (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.
#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.
Step 3: Implement the Delegate Protocol
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.
- (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]; }); }
Conclusion
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
.
Comments