NSURLSession(URL会话)
概述
NSURLSession
类和其相关的类提供了下载内容的API.此类提供了丰富的代理方法集合以支持验证并且在应用没有运行时,或者在iOS中处于挂起状态时,也能够在后台下载。
NSURLSession
类原生支持data
,file
,ftp
,http
和https
。
使用NSURLSession
API,应用可能创建一个或者多个sessions
,每一个session
协调一组相关的数据传输任务。比如,写一个web
浏览器,应用可能为每一个标签页或者窗口创建一个session
,或者为交互中的用户创建一个session
并为后台下载创建另一个session
。在每一个session中应用为其添加一系列的任务,每一个任务代表一个指定URL请求。
一个URLSession中的任务共享一个配置对象,配置对象定义了连接行为,比如同时连接到同一个服务器的最大连接数量,是否允许蜂窝网络连接,等等。会话的行为部分由创建其配置对象时调用的方法决定:
-
单例共享的
session
(没有配置对象)用于创建基本的请求。它不能够自定义。但是如果是非常局限的请求,使用它是个非常不错的开始。调用sharedSession
访问这个session。有关其限制的更多信息,请参阅该方法的讨论。 -
临时会话与默认会话类似,但不要将缓存,
Cookie
或凭据写入磁盘。 您可以通过调用NSURLSessionConfiguration
类中的ephemeralSessionConfiguration
方法来创建临时会话配置。 -
后台会话允许应用在未运行时在后台执行内容的上传和下载。 您可以通过调用
NSURLSessionConfiguration
类中的backgroundSessionConfiguration:
方法来创建后台会话配置。NSURLSession
类和其相关的类提供了下载内容的API.此类提供了丰富的代理方法集合以支持验证并且在应用没有运行时,或者在iOS中处于挂起状态时,也能够在后台下载。
session
配置对象同时包含URL缓存和Cookie存储对象的引用,这些引用在创建请求或者处理响应时可能会用到,取决于配置和请求类型。
session
的任务也共享一个代理,允许你在认证失败,当数据从服务器返回,当准备缓存数据等多个事件发生时提供和获取信息。对于所有后台下载和上传,必须提供一个遵循了NSURLSessionDownloadDelegate
协议的代理。另外如果不需要代理提供的任何功能,可以在创建session时传入nil,也就是不提供其代理对象。
重要
session对象对代理是强引用直到应用退出或者显式地销毁session。如果不销毁session,在应用未退出前会一直泄漏内存。
在session中创建任务以上传数据到服务器,然后从服务器获取数据转换为文件放在磁盘或者转换为NSData存放在内存中。NSURLSession
提供了三种任务类型:
-
NSURLSessionDataTask
使用NSData发送和接收数据。通常用在与服务器耗时较短的、交互的(自己的理解与后台相对,不在后台运行)请求。 -
NSURLSessionUploadTask
和NSURLSessionDataTask
相似,也发送数据(通常以文件形式),支持在应用未运行的情况下在后台上传。 -
NSURLSessionDownloadTask
以文件的形式获取数据,支持在应用未运行的情况下在后台下载和上传。
和大多数网络API一样,NSURLSession
API是异步的。它以两种方式中的一种返回数据,基于调用的方法:
- 当传输成功或者失败时,调用完成处理代码块(completion handler block);
- 当传输完成,数据到达时调用
session
的代理方法。
向代理分发信息之外,NSURLSession
API提供状态和进度属性。如果需要可以查询它们,根据当前的任务状态做程序决议。
URL session
同时支持取消、重启、恢复和挂起任务,并能够在挂起、取消、下载失败的位置恢复任务。
URL Session类层级
NSURLSession
API包含以下类:
- NSURLSession - 会话对象
- NSURLSessionConfiguration - 初始化
session
对象时用到的配置对象 - NSURLSessionTask - 会话中任务的基类
- NSURLSessionDataTask - 获取URL内容的任务,获取的数据转换为NSData对象
- NSURLSessionUploadTask - 上传文件的任务,然后从URL获取内容,获取的数据转换为NSData对象
- NSURLSessionDownloadTask - 从URL获取内容的任务,获取的内容转换为磁盘上的临时文件。
- NSURLSessionStreamTask - 创建TCP/IP连接的任务
- NSURLSessionDataTask - 获取URL内容的任务,获取的数据转换为NSData对象
另外,NSURLSession
API提供了四个协议,应用可以实现这些协议定义的代理方法,从而对 `session和任务行为提供更细粒度的控制。
- NSURLSessionDelegate - 定义代理方法以处理会话级事件
- NSURLSessionTaskDelegate - 处理所有任务类型通用的任务级事件
- NSURLSessionDataDelegate - 定义委托方法来处理特定于数据和上传任务的任务级事件
- NSURLSessionDownloadDelegate - 定义委托方法以处理特定于下载任务的任务级事件
- NSURLSessionStreamDelegate - 定义委托方法以处理特定于'stream'任务的任务级事件
最后,NSURLSession也使用其他常用的类,比如NSURLConnection
和NSURLDownload
。其中一些共享类如下:
- NSURL - 包含URL的对象
- NSURLRequest - 封装与URL请求向关联的元数据,包括URL,请求方法,等等。
- NSURLResponse - 封装与服务器响应对象相关的元数据,比如内容的MIME类型和长度。
- NSHTTPURLResponse - HTTP请求相关的特指的额外的元数据,比如响应头
- NSCachedURLReponse - 封装NSURLResponse对象以及服务器响应的实际主体数据,用于缓存目的。
验证和TLS自定义
当服务器请求身份验证或在TLS协商期间提供凭据时,URL会话将调用其代理上的方法,以允许以自定义方式处理身份验证或证书验证。 它调用的方法取决于您是处理任务特定的挑战(task-specific challenge)还是会话范围的挑战(session-wide challenge)。 表1显示了两者之间的差异。
chanllenge_deferences.png对于任务特定的挑战,会话调用它代理的URLSession:task:didReceiveChallenge:completionHandler:
方法。
对于会话范围的验证挑战,会话调用它代理的URLSession:didReceiveChallenge:completionHandler:
方法,如果有这个方法。否则,调用它代理的URLSession:task:didReceiveChallenge:completionHandler:
方法。
-
如果不实现这些方法,当一个请求需要客户端验证时,URL尝试验证如下:
使用提供的身份验证信息作为请求的URL的一部分,如果可用 -
通过在用户的钥匙串(在macOS中)或应用程序的钥匙串(在iOS中)查找Internet密码和证书,
然后,如果凭据不可用,或者服务器拒绝凭据,连接将继续进行而不进行身份验证。 对于HTTP和HTTPS请求,连接尝试失败,并给出相应的HTTP状态代码,并可能提供可选的内容(例如私有站点的公开版本)。 对于其他URL类型(如FTP),返回连接失败的结果。
** 注意 **
Kerberos认证是透明处理的。 这里描述的委托方法不适用于Kerberos身份验证。
应用传输安全(App Transport Security, ATS)
从iOS 9.0和OS X v10.11开始,对于使用NSURLSession所做的所有HTTP连接,默认情况下启用一个名为App Transport Security(ATS)的新安全功能。 ATS要求HTTP连接使用HTTPS(RFC 2818)。
有关详细信息,请参阅 Information Property List Key Reference中的NSAppTransportSecurity。
使用URL Session
使用NSURLSession
类创建请求”
- 创建会话配置对象。对于后台会话,配置必须包含唯一的标识。保存这个标识,如果应用崩溃、退出或者挂起,使用它和会话重新关联起来。
- 创建会话,指定配置对象,代理可选。
- 在会话中创建任务对象,每一个任务代表一个资源请求。这个任务对象是
NSURLSessionTask
子类 -NSURLSessionDataTask
,NSURLSessionUploadTask
, 或者NSURLSessionDownloadTask
的对象,取决于要实现的行为。
每个任务处于挂起状态,当应用调用任务的`resume`之后,它开始下载指定的资源。
开启任务之后,会话调用它的代理方法,如下:
-
如果与服务器的初次握手需要连接级挑战(比如SSL客户端证书),NSURLSession调用
URLSession:task:didReceiveChallenge:completionHandler:
或者`URLSession:didReceiveChallenge:completionHandler: 代理方法,如前面验证和TLS自定义里面描述的一样。
关于会话验证代理方法的更多信息,查看URL Session Programming Guide。 -
如果任务数据由流(stream)提供,
NSURLSession
对象调用代理的URLSession:task:needNewBodyStream:
方法以获取NSInputStream
对象,这个对象为新的请求提供了请求体数据。 -
在想服务器上传请求提数据期间,代理周期性的接收
URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:
回调,报告上传进度。 -
服务器发送响应。
-
如果响应指示需要验证,会话调用它代理的
URLSession:task:didReceiveChallenge:completionHandler:
方法。回到第2步。 -
如果是一个HTTP重定向响应。
NSURLSession
对象调用代理的URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:
方法。代理方法调用提供的完成处理者,并传递NSURLRequest
对象(遵从重定向),一个新的NSURLRequest
对象(重定向到其他URL),或者nil
(把重定向响应体作为有效响应,并作为结果返回)。
* 如果遵从重定向,回到第2步;
* 如果代理没有实现这个方法,重定向会跟踪到最大重定向数。
-
通过
downloadTaskWithResumeData:
或者downloadTaskWithResumeData:completionHandler:
方法创建的(重新)下载任务,NSURLSession
调用代理的URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:
方法,并传入一个新的任务对象。 -
对于
data task
,NSURLSession
对象调用代理的URLSession:dataTask:didReceiveResponse:completionHandler:
方法。决定是否把数据任务(data task
)转换为下载任务(download task
),然后调用完成回调以继续接收或者下载数据。 -
从服务器传输数据期间,代理周期性地接收任务级回调以报告传输进度。
对于数据任务,会话在接收到实际的数据片段时调用代理的
URLSession:dataTask:didReceiveData:
方法。对于下载任务,会话调用委托的·、
URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:
方法,并传入已成功写入磁盘的字节数。 如果用户告诉应用暂停下载,调用cancelByProducingResumeData:
方法取消该任务。然后,如果用户要求应用恢复下载,将返回的恢复数据传递给
downloadTaskWithResumeData:
或downloadTaskWithResumeData:completionHandler:
方法,以创建一个新的下载任务,继续下载。 (回到步骤1) -
对于
data task
,NSURLSession
对象可能调用代理的URLSession:dataTask:willCacheResponse:completionHandler:
方法。之后应用应该决定要不要缓存。如果不实现这个方法,默认的行为是使用会话配置对象中的指定的缓存策略。 -
如果响应是多部分编码的,则会话可以再次调用委托的didReceiveResponse方法,然后再执行零次或多次额外的didReceiveData调用。 如果发生这种情况,请转到步骤8(处理didReceiveResponse调用)。
-
如果一个下载任务成功完成,
NSURLSession
对象调用任务的URLSession:downloadTask:didFinishDownloadingToURL:
,并传入临时文件的位置。应用必须从文件读取响应数据或者在代理方法返回之前把文件移动到持久存储的位置。 -
任何任务完成时,
NSURLSession
对象调用代理的URLSession:task:didCompleteWithError:
方法,并传入一个error
对象或者nil(如果任务顺利完成)。
如果任务能够恢复,`NSError`对象的`userInfo`字典包含与` NSURLSessionDownloadTaskResumeData `键对应的值。应用应该使用这个值调用 `downloadTaskWithResumeData: `或者`downloadTaskWithResumeData:completionHandler:`创建一个新的下载任务以继续已有的下载。
如果任务不能恢复,应用应该创建一个新的下载任务,从开始重新下载。
不论哪种情况,只要不是服务器错误导致的传输失败,回到第3步(创建和恢复任务对象)。
> ** 注意 **
NSURLSession不通过`error`参数报告服务器错误。代理从`error`参数接收到的错误都是客户端错误,比如不能解析或者连接到主机。错误码在`URL Loading System Error Codes`中有描述。
> 服务端错误由`NSHTTPURLResponse ` 对象中的`HTTP`状态码报告。更多信息,参阅`NSHTTPURLResponse` 和 `NSURLResponse ` 类的文档。
- 如果不在需要
session
对象,可以调用invalidateAndCancel
(取消未完成的任务) 或者finishTasksAndInvalidate
(在作废会话对象之前允许未完成的任务继续完成)以作废其对象。如果不作废session
对象,它会在应用程序终止时自动消失(除非它是有激活任务的后台会话)。
作废会话之后,当所有未完成的任务取消或者完成时,会话对象调用代理的`URLSession:didBecomeInvalidWithError: `方法。当这个代理方法返回时,代理对象清除其对代理的强引用。
如果应用取消了正在进行中的下载,NSURLSession
调用代理的URLSession:task:didCompleteWithError:
,和发生错误一样。
后台传输注意事项
因为重启应用(或者等待用户重新加载)代价相对更高,一些功能在后台会话中是不可用的。结果就是:
-
session
必须为事件分发提供代理。因为应用可能会退出并且在传输的过程中重新加载,不支持完成回调代码块。(对于上传和下载,这些代理的行为与进程内传输相同)。 - 只支持
HTTP
和HTTPS
协议。不支持其他内置的网络协议和自定义的协议。 - 只支持上传和下载任务,不支持数据任务(
data task
) - Redirects are always followed.
- 系统范围内并发的后台传输的数量是有限制的。
- 如果后台任务无法满足系统指定的吞吐量限制,则可能被取消。 也就是说,如果长时间运行的任务在一段时间内没有发送或接收足够的数据,则可能被取消,之后再重新开始。 因此,尽可能使传输能够恢复很重要。
- 如果后台传输是在应用程序处于后台时启动的,则该任务被视为
discretionary
。 换句话说,它的行为就像一个会话中的任务,其配置对象的discretionary
属性为true
。
如果这些限制和你的应用需求冲突,你可以在非后台会话中下载远程资源到一个文件中。如果你这样做,当用户把你的iOS应用放到后台或者退出MacOS应用时,调用cancelByProducingResumeData:
方法暂停所有激活的下载。当用户把应用重新带到前台时,恢复下载。如果应用在获取恢复数据(resume data)之前就终止了,就不能再恢复下载。
** 注意 **
后台会话是优化后用于传输数量较少、能够在必要时恢复的大资源。你可能想调研优化服务器端行为的方法,以便于此类使用,比如:
- 向发送或者接收zip或者tar的终端发送请求而不是许多独立的调用(making several individual calls)
- 向发送或接收增量差异的终端发送请求,以在客户端和服务器之间进行复制。
- 向返回上传标识的终端发送请求,之后能够被用来追踪和恢复向服务器传输数据。
- 添加中间web服务,此服务代理请求到权威web服务,以便于任何上述优化。
NSCoping行为
会话和任务遵守NSCoping协议,如下:
- 当应用拷贝会话或者任务时,获取到的是相同的对象。(浅拷贝)
- 当应用拷贝配置对象时,获取了一个新的副本能够单独修改。(深拷贝)
线程安全
URL session API自身是是线程安全的。能够随意的在任何线程上下文创建会话和任务,当代理方法调用提供的完成处理者(completion handler)时,一切将自动安排在正确的代理队列上。
警告
URLSessionDidFinishEventsForBackgroundURLSession:
代理方法可能会在其他线程调用。然而,在iOS中,你对这个方法的实现可能需要调用app代理的application:handleEventsForBackgroundURLSession:completionHandler:
方法中提供的完成处理者。必须在主线程中调用完成处理者。