8a6c361f0646a28f7ebeae0ec3b17163
NSURLSession的那些事儿

iOS的网络框架都是基于URL Loading System实现的,而这其中最重要也是最常用的就是 NSURLSession了,它里面的API涉及到了URL Loading System几乎所有方面,我觉得有必要重新回顾一下技术细节,正所谓“温故而知新”。

目录

  1. NSURLSession 概览
  2. NSURLSessionConfiguration
  3. NSURLSessionTask
  4. NSURLSessionDelegate
  5. NSURLSessionTaskTransactionMetrics
  6. 缓存策略
  7. 安全策略

1. NSURLSession 概览

NSURLSession是从iOS7开始的,这之前都使用的是NSURLConnection。目前主流的网络库框架如AFNetworking 3.0、Alamofire、SDWebImage 3.8.0 都使用了NSURLSession

从名字即可看出URL + Session是用URL来会话通信的,即iOS客户端使用NSURLSession来通过URL与服务端建立会话,然后完成信息交互。

类型

从会话的时机和线程数来说,NSURLSession分为三种类型,我们可以通过NSURLSessionConfiguration来指定:

  • Default sessions(默认会话) 使用了持久的磁盘缓存,并且将证书存入用户的钥匙串中。
  • Ephemeral sessions(临时会话) 没有像磁盘中存入任何数据,与该会话相关的证书、缓存等都会存在RAM中。因此当你的App临时会话无效时,证书以及缓存等数据就会被清除掉。
  • Background sessions(后台会话) 除了使用一个单独的线程来处理会话之外,与默认会话类似。不过要使用后台会话要有一些限制条件,比如会话必须提供事件交付的代理方法、只有HTTP和HTTPS协议支持后台会话、总是伴随着重定向。仅仅在上传文件时才支持后台会话,当你上传二进制对象或者数据流时是不支持后台会话的。当App进入后台时,后台传输就会被初始化。

从会话的数据行为来说,NSURLSession也分为三种类型:

  • Data Task(数据任务) 负责使用NSData对象来发送和接收数据。Data Task是为了那些简短的并且经常从服务器请求的数据而准备的。该任务可以每请求一次就对返回的数据进行一次处理。
  • Download task(下载任务) 以表单的形式接收一个文件的数据,该任务支持后台下载。
  • Upload task(上传任务) 以表单的形式上传一个文件的数据,该任务同样支持后台下载。

总的来说,NSURLSessionURL Loading System的其他模块之间的协作关系如下图:

NSURLSession协作关系

NSURLSession协作关系

接下来,本文将详细介绍各个模块。

2. NSURLSessionConfiguration

NSURLSessionConfiguration用来配置Session的属性,前面提到三种类型的NSURLSession可以分别对应三种创建NSURLSessionConfiguration的方法:

//使用全局的cache,cookie和credential storage objects来创建configuration对象。
defaultSessionConfiguration;
//这个configuration用于“private” sessions,还有对于cache, cookie, or credential storage objects的非永久存储
ephemeralSessionConfiguration;
//做远程push通知或是应用程序挂起的时候就要用到这个configuration。
backgroundSessionConfigurationWithIdentifier:(NSString *)identifier;

NSURLSessionConfiguration还有很多重要的配置属性:

NSURLSessionConfiguration *defaultConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
// 限制网络只能使用wifi
defaultConfig.allowsCellularAccess = NO;
// 所有请求只接受JSON数据
[defaultConfig setHTTPAdditionalHeaders:
      @{@"Accept": @"application/json"}];
// 设置特定代理
let proxyDict: [String: Any] = [
  "HTTPSEnable":true,
  "HTTPSProxy":"172.30.24.41",
  "HTTPSPort":8888
]
defaultConfig.connectionProxyDictionary = proxyDict
// 限制一个主机只有一个连接
defaultConfig.HTTPMaximumConnectionsPerHost = 1;
// 支持的TLS 最小版本
defaultConfig.TLSMinimumSupportedProtocol = kTLSProtocol11
// 不使用HTTP 的pipelining特性
defaultConfig.HTTPShouldUsePipelining = false
// 设置cookies
defaultConfig.HTTPShouldSetCookies = true

3. NSURLSessionTask

NSURLSessionTask代表了一个请求的生命周期,中途可以取消、挂起、恢复。

具体地,有三个重要的子类。

NSURLSessionTask子类

NSURLSessionTask子类

3.1 NSURLSessionDataTask

这是最普通的task,使用HTTP的GET/POST请求从服务端获取数据,返回数据格式是NSData,自己可以选择转化成XML、JSON、UIImage等。
可以使用两种方法创建DataTask:

/* Creates a data task with the given request.  The request may have a body stream. */
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request;

/* Creates a data task to retrieve the contents of the given URL. */
- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url;
3.2 NSURLSessionUploadTask

这是用来上传数据的Task, 继承自NSURLSessionDataTask,使用POST方式进行表单提交。
可以使用三种方法创建UploadTask:

/* Creates an upload task with the given request.  The body of the request will be created from the file referenced by fileURL */
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL;

/* Creates an upload task with the given request.  The body of the request is provided from the bodyData. */
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData;

/* Creates an upload task with the given request.  The previously set body stream of the request (if any) is ignored and the URLSession:task:needNewBodyStream: delegate will be called when the body payload is required. */
- (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request;
3.3 NSURLSessionDownloadTask

这是用来下载数据的Task。相对来说这种Task复杂一些,因为它可以支持后台下载、暂停、断点续传。
可以使用三种方法创建DownloadTask:

/* Creates a download task with the given request. */
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request;

/* Creates a download task to download the contents of the given URL. */
- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url;

/* Creates a download task with the resume data.  If the download cannot be successfully resumed, URLSession:task:didCompleteWithError: will be called. */
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData;

暂停下载
暂停下载操作是调用Download Task中的cancelByProducingResumeData方法。在调用上述方法时会通过Closure回调的形式返回一个ResumeData,拿到该 ResumeData后可以将它进行磁盘的持久化存储,便于下次继续下载。

//暂停下载
...
downLoadTask?.cancelByProducingResumeData({ (resumeData) in
   if let res = resumeData  {
      NSUserDefault.standardDefaults().setObject(res, forKey: kResumeData)
  }
})
...

恢复下载
如果我们暂停下载后存储了ResumeData,那么以后我们可以据此重新恢复下载。ResumeData中存储的并不是我们上次下载的数据Data,而是存储了下载地址和上次下载的位置等相关的信息,通过该ResumeData我们可以接着上次的文件进行下载。

//恢复下载 
let resumeData: NSData? = NSUserDefault.standardDefaults().objectForKey(kResumeData) as? NSData
if nil != resumeData {
    downloadTask = downloadSession?.downloadTaskWithResumeData(resumeData!)
} else {//从头开始下载
    downloadTask = downloadSession?.downloadTaskWithUrl(fileUrl)
}

ResumeData存储的是下载地址和下载位置等信息,可以参见下图的plist文件:
ResumeData

ResumeData

3.4 NSURLSessionStreamTask

从iOS9之后又引入了一个新的Task——NSURLSessionStreamTask,我们就可以通过它直接使用socket连接了,可以读取输入流和写入输出流等等。用的比较少,暂不细说。

4. NSURLSessionDelegate

上面说了几种Task,所有的Task执行都需要有任务回调的方法。简单地,可以直接使用Block,如:

- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;

- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;

- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;

另外,还可以使用代理模式,在NSURLSessionConfiguration中设置delegate

所有的delegate基类是NSURLSessionDelegate,包含与整个Session相关的协议方法, 其有三个可选的协议方法:

// 一个Session只有遇到系统错误或者显式地无效化时才会变成无效的,收到这个消息后,Session就不再接收消息了。
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error;

// 遇到鉴权挑战时,可以让代理实现提供认证证书的方法。如果未实现这个方法,将会执行默认的处理方法。
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
                                             completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler;

// 后台session在执行完后台任务后执行的方法。收到这个消息,代表之前队列里所有的消息都发送完成,可以调用完成的回调了
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session

它的子协议是NSURLSessionTaskDelegate,包含与具体的Task相关的协议方法。其中主要的几个方法是:

```
//HTTP请求重定向时会调用这个方法,可以在此修改请求地址。

top Created with Sketch.