NSURLSession

NSURLSession 以及相关的类,提供了丰富的 API 通过 HTTP 协议来获取网络上的内容。NSURLSession 不仅提供了大量的代理方法来实现授权,同时也支持当 app 在 iOS 处于挂起状态时继续进行网络下载活动。

在实际的使用中,app 可能会创建多个对话( session ),每个对话又包含了多个网络下载任务( data transfer tasks )。

和其他的网络请求 API 类似,NSURLSession 也是高度异步的。如果采用系统提供的代理( system-provide delegate ),就必须实现一个事件完成处理代码块( completion handler block ),用来对网络任务完成时对下载的数据进行处理(失败的时候会提供一个Error)。亦或者,可以为其提供一个自定义的代理对象,这样的话,任务对象会在从服务端接收到数据时,调用相关的代理方法( 如果是整个文件的下载,则是在下载结束的时候调用 )。( 事件完成处理代码块相对于自定义代理对象有更高的优先级。如果在穿件任务的时候同时传入了事件完成处理代码块,那么相关的代理方法就不会被调用。)

NSURLSession 还提供了状态和进度的属性。还支持任务的取消,重启(继续)或者挂起。在app退出时,支持任务取消,失败或者恢复。

一个会话中的任务的具体执行取决于三个方面:

  • session 的类型(由配置对象( NSURLSessionConfiguration )决定
  • 任务类型
  • 任务创建时,app 是否在前台运行

NSURLSession 概况

会话类型 ( Types of Sessions )

  • 默认类型( Default ):该类型的行为和其他基于 Foundation 的网络方法类似,可以将缓存可持久化的存储到磁盘,把授权信息存储到用户的钥匙串( user’s keychain )。
  • 临时类型( Ephemeral ):不会存储任何信息到本地磁盘。所有的缓存,授权等等都存储在RAM中,并和会话紧密关联。当会话失效是,相关数据也都会被从 RAM 清除。
  • 后台类型( Background ):何默认模式类似,除了该类型下是单独开辟了一个进程进行数据传输。

任务类型 ( Types of Tasks )

  • 数据任务( Data Task ):使用 NSData 对象来发送和接受数据。数据任务一般都是一个短暂的,临时性的网络请求任务。数据任务会每次返回一部分接收到的数据( 使用事件完成处理代码块则是一次性返回 )。数据任务并不会把数据存储到文件中,所以不支持后台会话类型。
  • 下载任务( Download Task ):以文件的形式接收数据,并且支持后台会话类型。
  • 上传任务( Upload Task ):以文件的形式上传数据,并且支持后台会话类型。

创建和设置对话 ( Creating & Configuring a Session )

NSURLSession 提供了大量的配置选项

  • 私有化的缓存,cookie,授权认证存储空间,针对个别对话的协议。
  • 身份验证,跟指定的单个网络任务请求或者会话绑定。
  • 文件化的上传和下载,鼓励将文件内容分割。
  • 单主机的最大接入数限制。
  • 在资源在一个确定的时间无法完成下载时,触发超时操作。
  • 最大和最小的 TLS 版本支持。
  • 自定义代理( proxy )字典。
  • 操控 cookie 策略。
  • 操控 HTTP 管线( pipeline )行为。

因为大部分的配置都在一个 NSURLSessionConfiguration 对象中进行,所以可以服用其中相同的部分。在什么一个 NSURLSession 实例的过程如下:

  • 创建一个定义了会话和会话中任务行为的 NSURLSessionConfiguration 对象;
  • 作为一个可选的选项,可以创建一个代理对象来处理接收到的网络数据或者其他相关的会话和任务的操作,如服务器授权,决定是否将资源加载请求转变成下载任务等等。如果不设置代理对象,NSURLSession 对象会使用系统内建提供的代理。这样就可以在需要的是否轻松的使用 sendAsynchronousRequest:queue:completionHandler: 进行下载。( 如果 app 需要进行后台的网络传输请求,则必须声明自定义的代理对象。 )

可以复用会话的配置对象来创建新的会话,除了后台任务的配置对象,因为另个不同的后他会话不能拥有相同的标示符。

在创建会话的时候,会话的配置对象是通过深拷贝( deep copy )获取的,所以可以在任何时候对配置对象进行修改,但修改只会对后续采用该配置对象的会话有效,对之前创建的对话不产生任何影响。

使用系统内建代理请求数据( Fetching Resources Using System-Provide Delegates )

最简洁的使用 NSURLSession 的方式是使用 sendAsynchronousRequest:queue:completionHandler: ,使用该方法,只需要二外提供两块代码:

  • 创建一个 NSURLSessionConfiguration 对象,以及基于该对象的一个 NSURLSession
  • 创建一个用来处理下载完成的数据的事件完成处理代码块。
1
2
3
4
5
6
7
8
NSURLSession *delegateFreeSession = [NSURLSession sessionWithConfiguration:defaultConfigObject
delegate:nil
delegateQueue:[NSOperationQueue mainQueue]];

[[delegateFreeSession dataTaskWithURL:[NSURL URLWithString:@"http://www.example.com/"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(@"Got response %@ with error %@.\n", response, error);
NSLog(@"DATA:\n%@\nEND DATA\n", data);
}] resume];

使用自定义代理请求数据( Fetching Data Using a Custom Delegate )

如果使用自定义的代理对象,必须实现下列方法:

  • URLSession:dataTask:didReceiveData: 将请求获得数据分块返回
  • URLSession:task:didCompleteWithError: 通知数据接收完成

下载文件( Downloading Files )

下载文件和接收数据很类似,需要实现下列方法:

  • URLSession:downloadTask:didFinishDownloadingToURL: 提供一个临时的下载文字。
  • URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite: 提供下载进程的状态信息。
  • URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes: 回馈恢复之前下载的失败的任务成功。
  • URLSession:task:didCompleteWithError: 下载完成。

如果下载任务是在一个后台会话中进行的,在 app 挂起时,下载仍然会继续。如果是在标准会话或者临时会话中,那么下载任务需要在程序再起启动时重新开始。

如果在下载进行的时候,用户请求暂停下载,app 可以通过调用cancelByProducingResumeData: 来暂停。之后可以通过downloadTaskWithResumeData: 或者downloadTaskWithResumeData:completionHandler: 来创建一个新的下载任务来继续之前的下载。

如果下载失败,URLSession:task:didCompleteWithError: 代理方法会被调用,并传入一个 NSError 对象。如果任务仍然可以继续( resume ),NSError 对象的 userInfo 字典包含了一个对应于“NSURLSessionDownloadTaskResumeData” 的值。然后在适合的时机再通过downloadTaskWithResumeData:downloadTaskWithResumeData:completionHandler: 来重启下载。

上传主体内容( Uploading Body Content )

app 可以再 POST 请求的时候通过如下三种方法提供主题内容:

  • 使用NSData,如果 app 已经将数据加载到了内存就没必要去销毁它。
  • 使用文件,若果要上传的数据在磁盘的某个文件中,或者对于 app 来讲,将数据写到文件内比较好。
  • 使用数据流,如果数据是通过网络获得或者从已经存在的 NSURLConnection 转换获得,那么提供请求作为数据流。

不管采取哪种方式,如果有提供自定义的会话代理,代理需要实现URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend: 来捕获上传的进度信息。

另外,如果通过数据流来提供请求的主体,则必须声明自定义的会话代理。并且实现URLSession:task:needNewBodyStream: 方法。

通过NSData上传主体内容( Uploading Body Content Using an NSData Object )

如果使用 NSData 来上传主体内容,应该使用uploadTaskWithRequest:fromData: 或者uploadTaskWithRequest:fromData:completionHandler: ,通过 fromData 参数来添加数据。

会话对象通过计算NSData来确定 Content-Lenght 。app 必须额外在请求对象中添加服务端需要的头部信息。

通过文件上传主体内容(Uploading Body Content Using a File)

如果使用文件来上传主体内容,应该使用 uploadTaskWithRequest:fromFile: 或者 uploadTaskWithRequest:fromFile:completionHandler: ,提供文件的 URL 来添加数据。

会话对象通过计算 NSData 来确定 Content-Lenght 。app 必须额外在请求对象中添加服务端需要的头部信息。

通过数据流上传主体内容( Uploading Body Content Using a Stream )

如果使用数据流来上传主体内容,应该使用 uploadTaskWithStreamedRequest: ,提供请求对象来获取与之相关联的数据流。

app 必须额外在请求对象中添加服务端需要的头部信息。

因为会话对象不一定能够从请求重读数据,对于必须重新尝试请求的情况,app 必须提供新的数据流。为此,app 应该实现 URLSession:task:needNewBodyStream: 方法。app 应该在这个方法中获取或者创建一个新的主体数据流,然后通过调用事件完成处理代码块来传入新的数据流。( 因为 app 必须实现 `URLSession:task:needNewBodyStream:` 代理方法,因为会通过数据流来提供主体,这个方法和系统内建代理会产生冲突。)

通过下载任务上传主体内容( Uploading a File Using a Download Task )

如果要通过下载任务来实现主体内容上传,app 必须在创建下载请求的时候,提供 NSData 对象或者主体数据流作为 NSURLRequest 对象的内容。

如果提供的数据使用数据流,app必须实现URLSession:task:needNewBodyStream:代理方法,用于在授权失败的时候提供一个新的主体数据流。

处理身份验证和自定义TLS( Transport Layer Security )链验证( Handling Authentication and Custom TLS Chain Validation )

如果远程服务器返回的状态码表明需要进行认证,而这个认证有事链接层级别的话(例如 SSL 客户端证书),NSURLSession 会调用一个授权认证代理方法。( Kerberos身份验证是透明地处理 )

  • 对于会话层级别的授权请求 – NSURLAuthenticationMethodNTLMNSURLAuthenticationMethodNegotiateNSURLAuthenticationMethodClientCertificate 或者 NSURLAuthenticationMethodServerTrustNSULRSession 对象会调用会话代理的 URLSession:didReceiveChallenge:completionHandler: 方法。如果 app 没有实现该会话代理方法,那么会代用任务代理的 URLSession:task:didReceiveChallenge:completionHandler: 方法来处理授权。

  • 对于非会话层级别的请求 NSURLSession 对象调用会话代理的URLSession:task:didReceiveChallenge:completionHandler: 方法来处理,如果提供了会话的代理同时也需要去处理授权认证,那么必须在任务级别也处理授权或者在任务中明确的调用每个会话来处理。会话代理方法 URLSession:didReceiveChallenge:completionHandler: 不会在非会话层级别的授权请求中被调用。

当对于一个基于数据流上传主体的任务认证失败的时候,任务不能安全的倒回并且重使用之前的数据流。NSRLSession 对象会调用代理的 URLSession:task:needNewBodyStream: 方法来获取一个能够为请求提供新的主体数据的新的 NSInputStream 对象。( 如果主体数据是基于 NSData 或者文件的, 该调用不会进行 )。

处理iOS后台活动机制(Handling iOS Background Activity)

如果在 iOS 的 app 中使用了 NSURLSession ,app 会在下载任务完成时重新启动。此时,app 的application:handleEventsForBackgroundURLSession:completionHandler:app 代理方法会得到响应,并重现创建一个相关的会话,存储完成处理句柄,以及在会话调用会话代理的 URLSessionDidFinishEventsForBackgroundURLSession: 方法时调用句柄。

0%