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 thoughNSURLSession
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.
从开发者的角度,对于iOS7和OS X Mavericks最显著的变化就是引入了 NSURLSession 。虽然乍一看,NSURLSession 有点令人生畏,但重要的是要了解它是什么,它和 NSURLConnection 有什么关系和差异。在本系列 Networking with NSURLSession 教程中,我将会带你了解 NSURLSession 有关的基本内容,你也可以在你的应用程序中使用这种新技术的优势。
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.
你可能会问这样一个问题:为什么正当我们愉快的使用 NSURLConnection的时候,有必要引入 NSURLSession 吗?然而,问题恰恰就在于,你是否真的使用 NSURLConnection 很愉快?莫非你忘了你曾经咒骂吐槽 NSURLConnection 了,没错,大多数 Cocoa 开发者曾有这样的经历,所以Apple才决定重新设计一种更优雅的解决方案以适应于现代网络开发。
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.
尽管 NSURLSession 和 NSURLConnection 的运行有很多共同点,但是在最基础的层面上,它们还是有着很大的不同。Apple 引入类似于 NSURLConnection 概念的 NSURLSession ,你将会在本系列 Networking with NSURLSession 教程的这篇文章中了解到 NSURLSession 是现代的,更易于使用的,而且是附有为移动应用设计的初衷。
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.
在讨论 NSURLSession 和 NSURLConnection 的不同之处之前,不妨先仔细了解一下 NSURLSession 是什么。对于 NSURLSession ,它仅仅是可以在 iOS 和 OS X应用程序中使用的一个类,其就像 NSURLConnection 一样是一种用于网络开发的技术。
NSURLSession
and NSURLConnection
both provide an API for interacting with various protocols, such as HTTP
and HTTPS
. The session object, an instance of theNSURLSession
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.
NSURLSession 和 NSURLConnection 都提供了与各种协议,诸如 HTTP 和 HTTPS ,进行交互的API。会话对象,NSURLSession 类对象,就是用于管理这种交互过程。它是一个高度可配置的容器,通过使用其提供的APPI,可进行细粒度的管理控制。它提供了在 NSURLConnection 中的所有特性,此外,它还可以实现 NSURLConnection 不能完成的任务,例如实现私密浏览。
The basic unit of work when working with NSURLSession
is the task, an instance ofNSURLSessionTask
. There are three types of tasks, data tasks, upload tasks, anddownload tasks.
使用 NSURLSession 最基本单元就是任务(task),这个是 NSURLSessionTask 的实例。有三种类型的任务:data task,upload task 和 download task。
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.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.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
. TheNSURLSession
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.
正如你所想的,异步性是NSURLSession的关键概念。使用 NSURLSession 的 API 返回数据可以通过调用完成处理程序块或者通过会话的委托。NSURLSession 的 API 在设计的时候充分考虑了灵活性,关于这一点,你将会在本教程的后面体会到。
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.
正如我之前提到的,NSURLSession 即是指一种新的网络开发技术,同时也是一个类。NSURLSession 的 API 包括若干个类,但是 NSURLSession 是关键的部件用于发送请求和接收响应,而会话对象的配置是由 NSURLSessionConfiguration 实例进行处理。NSURLSessionTask 和其三个具体的子类总是和会话对象关联在一起,因为会话对象创建了任务对象。
Both NSURLSession
and NSURLConnection
rely heavily on the delegation pattern. TheNSURLSessionDelegate
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.
NSURLSession 和 NSURLConnection 都采用了委托代理模式。NSURLSessionDelegate 委托协议声明了少量的委托方法用于处理会话事件。除此之外,NSURLSessionTask 和其子类都各自有一个委托协议用于处理任务事件。
The NSURLSession
API builds on top of classes that you're already familiar with, such asNSURL
, NSURLRequest
, and NSURLResponse
.
NSURLSession 的 API 的使用涉及到你曾熟悉的类,例如:NSURL,NSURLRequest 和 NSURLResponse。
How does NSURLSession
differ from NSURLConnection
? This is an important question, because NSURLConnection
is not being deprecated by Apple. You can still useNSURLConnection
in your projects. Why should you use NSURLSession
?
那么 NSURLSession 和 NSURLConnection 有什么不同呢?这是一个关键的问题,因为 NSURLConnection 还未被 Apple 弃用。你仍可以是项目中使用 NSURLConnection。那何时使用 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.
首先要了解到,NSURLSession 实例对象是管理请求和响应的,这和 NSURLConnection 相似,但区别是管理请求配置的是一个长寿命对象,由 NSURLSessionConfiguration 类对象完成。通过 NSURLSessionConfiguration 类不仅提供了细粒度配置的 API 管理 NSURLSession 会话对象,而且将数据(请求主体)从元数据(metadata)中分离开来。NSURLSessionDownloadTask 通过直接将响应数据写入文件系统就很好的说明了这一点。
Authentication is easier and handled more elegantly by NSURLSession
. TheNSURLSession
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.
通过 NSURLSession 可以更容易的实现身份认证。NSURLSession 的 API 处理身份认证是在连接的基础上进行而非在请求的基础上进行,和 NSURLConnection 的处理方式是一样的。NSURLSession 的 API 提供了方便的 HTTP 协议处理选项,而且每一个会话可以根据你是如何配置会话而有一个单独的存储容器。
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
?
在引言中,我告诉你 NSURLSession 提供了一个现代化的接口,它与iOS 7优雅的集成整合。这种集成整合体现的一个例子是 NSURLSession 的上传下载是在进程外进行。 NSURLSession 也进行了优化以保持电池的使用寿命,提供暂停,取消和恢复任务,还支持 UIKit 的多线程处理的 API 。还有什么理由不爱 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 Filemenu, and select the Single View Application template from the list of iOS application templates.
实践出真知。在 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.
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
'ssharedSession
class method as shown below.
在使用 NSURLSession 的时候,重点是理解会话对象(它可是主角),NSURLSession 的实例。它处理请求和响应,配置请求,管理会话的存储和状态,等等。创建会话对象有多种方式,最快的是使用 NSURLSession 的 sharedSession 类方法。
- (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 globalNSURLCache
, NSHTTPCookieStorage
, and NSURLCredentialStorage
. This means that it works pretty similar to a default implementation of NSURLConnection
.
在视图控制器的 viewDidLoad 方法创建了 session 会话对象,这个会话对象已经适合我们例子的需要了,但是在大多数情况下可能需要多一点的灵活性。刚刚我们创建的 session 对象使用全局的 NSURLCache,NSHTTPCookieStorage 和 NSURLCredentialStorage。这样 session 的工作状态就和 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.
为了测试 session 对象的使用,我们使用 Apple 提供的数据查询接口: iTunes Store Search API ,这个开发接口易于使用且不需要身份验证,适合本例子。
To query the search API, we need to send a request tohttps://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.
我们发送请求并传递一些请求参数。正如我们之前所见,一个任务代表一个请求。使用查询接口请求数据,我们需要一个 Data task,NSURLSessionDataTask 的实例。修改 viewDidLoad 方法如下:
- (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.
有许多方法来创建一个任务,但是关键概念的理解是 session 对象创建且配置了任务。在这个例子中,我们调用 dataTaskWithURL:completionHandler:
方法,并传递 NSURL 对象和一个完成程序代码块作为参数。完成程序代码块有三个参数:请求响应的原始数据(NSData),响应对象(NSURLResponse)和一个错误对象(NSError)。如果请求成功,错误对象就为 nil。因为我们知道请求返回的是JSON数据作为响应,我们用 data 对象接收响应,并在终端中输出。
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.
对于 error 对象,如果请求失败或者遇到一个错误,那么传递给完成处理代码块的 error 对象(不是nil)就很重要了。换言之,如果请求得到一个 404 的响应,那么请求将会直到会话连接成功才得到响应。当然 error 对象也有可能是 nil。所以在使用 NSURLSession 和 NSURLConnection 的时候 error 对象是一个很重要把握的概念。
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 toresume
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.
编译运行程序在模拟器或者真机,然后观察 Xcode 的终端,会发现没有东西输出。是否出错了呢?正如我之前所说,NSURLSession 的 API 支持暂停,取消和恢复请求任务。这个和 NSOperation 很相似。任务有一个 state 状态属性指示一个任务是否正在运行 (NSURLSessionTaskStateRunning
),是否停止(NSURLSessionTaskStateSuspended
),是否取消(NSURLSessionTaskStateCanceling
)或者是否完成(NSURLSessionTaskStateCompleted
)。当一个会话对象创建了一个任务,这个任务初始时是处于停止状态。为了启动任务,我们需要调用 resume 方法去启动任务执行。修改 viewDidLoad 方法如下,重新运行,观察终端中的输出。
- (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
.
在前一个例子中,我们使用完成程序代码块去处理请求接收到的响应。也可以通过委托协议来实现相同过程。让我们看看借助 NSURLSession 和 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.
打开 MTViewController.h 然后创建如下的两个 outlet 。我们使用第一个 outlet,UIImageView 的实例,去显示下载的图像;第二个 outlet ,UIProgressView 的实例,去显示下载任务的进度。
#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.
打开项目的 storyboard,拖动 UIImageView 到视图中,然后和视图控制器中的 outlet 进行连接对应。重复这个过程完成添加进度条。
译者注:由于添加进度条之后,其默认值为0.5(打开 Xcode 右侧栏 Attributes inspector,其中 Progress 值默认为 0.5),那么由于下载一开始是从 0 开始,那么应该修改设置其为 0。或者使用代码,在 viewDidLoad 方法中 增加如下代码进行设置:
[self.progressView setProgress:0];
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.
在这个例子中,我们不使用 sharedSession 类方法创建会话对象,因为需要根据请求对会话对象进行配置。在viewDidLoad 方法中:
- (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]; }
译者注:由于代码中的图片链接地址有可能打不开(测试时,出现过),所以读者需要先将链接地址到浏览器中测试能否打开,若不能,则随便替换一个图片链接地址。译者就是无法打开该链接的图片,所以替换为这个链接:http://iphone.images.paojiao.cn//iphone/paper/201111/4/97108898/paojiao_08a86f17_280x420.jpg
To prevent any compiler warning from popping up, make sure to conform theMTViewController
class to the NSURLSessionDelegate
and NSURLSessionDownloadDelegate
protocols as shown below.
为了避免出现编译错误,确保 MTViewController 类遵循 NSURLSessionDelegate
和 NSURLSessionDownloadDelegate
协议。
#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.
在 viewDidLoad 方法中,我们通过调用 defaultSessionConfiguration
类方法创建 NSURLSessionConfiguration 类的实例。使用默认的会话配置将会使会话的行为和 NSURLConnection 实例的默认配置类似。
In this example, we create a NSURLSession
instance by invoking thesessionWithConfiguration:delegate:delegateQueue:
class method and pass thesessionConfiguration
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 thesession
's delegate to the view controller.
在这个例子中,我们创建创建 NSURLSession 实例是通过调用 sessionWithConfiguration:delegate:delegateQueue: 类方法,然后传入 sessionConfiguration 对象作为参数进行创建。我们设置视图控制器为会话的委托对象,然后传递 nil 给第三个参数。你可以暂时忽略第三个参数的含义。这和之前例子最主要的不同是我们设置 session 的委托对象为视图控制器。
To download the image, we need to create a download task. We do this by callingdownloadTaskWithURL:
on the session
object, passing an instance of NSURL
, and callingresume
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.
为了下载图像,我们要创建一个 Download 任务。我们在 session 会话对象上调用 downloadTaskWithURL: 方法,传入一个 NSURL 实例,在调用 resume 方法去开始下载任务。我们这里可以使用完成程序代码块处理接收请求响应,但是我这里想要使用委托方法进行实现。
To make all this work, we need to implement the three delegate methods of theNSURLSessionDownloadDelegate
protocol,URLSession:downloadTask:didFinishDownloadingToURL:
,
URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:
, andURLSession: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.
为了完成下载过程,我们需要实现 NSURLSessionDownloadDelegate 委托协议的如下三个方法:URLSession:downloadTask:didFinishDownloadingToURL: ,URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes: 和 didWriteData:totalBytesWritten:totalBytesExpectedToWrite: 。每一个委托方法的实现都比较简单。重点需要注意的是要使用 GCD(Grand Central Dispatch) 在主线程中更新用户界面(UI)。由于在 sessionWithConfiguration:delegate:delegateQueue: 方法中传入 nil 作为其第三个参数,操作系统自动为我们创建一个后台队列。在本例中是适用的,同时我们也要意识到这些委托方法是在后台线程中进行调用执行的,而非在主程序中。编译运行项目程序,观察结果。
- (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]; }); }
译者注:运行项目其结果:进度条显示图像下载进度(从0开始),下载完毕,进度条隐藏,图像显示。
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 ofNSURLSession
.
有了这两个例子,你应该对 NSURLSession 的基础知识,它和 NSURLConnection 的比较,它的优势有了一定的了解。在下一篇教程中,我们将深入了解 NSURLSession 的其它先进特性。