我们通常情况下可以不提供代理对象来使用NSURLSession相关的API。然而,如果要通过NSURLSession的API来做后台下载和上传工作时,或者是要以非默认的方式来处理身份认证和缓存,那么我们就必须要提供一个代理对象并遵守相应的session代理协议,任务代理协议,抑或是一些协议组合。其目的如下:
- 当使用下载任务时,NSURLSession对象通过代理给我们应用程序提供相应的下载数据。所有的后台下载和上传都需要代理,该代理对象必须提供
NSURLSessionDownloadDelegate
协议的代理方法。 - 代理可以处理身份认证询问;
- 代理可以基于数据流的方式上传到远程服务器;
- 代理可以决定是否遵守HTTP重定向;
- NSURLSession对象使用代理的方式给我们app传达每一个网络传输的状态。数据任务代理接受初始调用,可以把请求转换为下载连续调用,并提供远程服务器返回的数据块儿。
- NSURLSession对象可以使用代理的方式在传输完成之后告诉给app。
如果在URLSession中使用自定义的代理,那么URLSession的生命周期将会更加复杂。下面是在NSURLSession
中使用自定义代理的基本顺序:
- 创建一个session配置对象。对于后台session,需要给配置对象分配一个唯一标识符,保存该标识符并在程序发生了carsh、终止或者暂停之后重新关联session;
- 创建一个session,指定配置对象并可选地传入一个代理;
- 创建一个task(任务,存在于session里,一个任务对应一个请求);
每个任务在开始的时候都是处于暂停状态。需要我们对任务调用resume方法之后便开始下载指定的资源。这些任务对象都是
NSURLSessionTask
子类(包括有NSURLSessionDataTask
,NSURLSessionUploadTask
, 和NSURLSessionDownloadTask
)的实例,具体是哪一个类的实例取决于自己的实现。尽管可以给一个session添加多个task,但为了简单起见,下面的步骤都是基于单个任务来讨论。若在使用NSURLSession类时没有提供代理,我们需要在创建任务时提供completionHandler参数,否则将无法从类中获取数据。 如果远程服务器返回的状态码表示需要进行身份认证,并且该身份认证是基于连接级别的询问(比如SSL客户端证书),NSURLSession会调用身份认证的代理方法。
- a)、对于会话层询问——NSURLAuthenticationMethodNTLM, NSURLAuthenticationMethodNegotiate, NSURLAuthenticationMethodClientCertificate, 或者 NSURLAuthenticationMethodServerTrust。该NSURLSession对象会调用session的代理方法URLSession:didReceiveChallenge:completionHandler: 。如果我们在程序中并没有提供session的代理方法,那么该session对象会调用其任务的代理方法URLSession:task:didReceiveChallenge:completionHandler:来处理询问。
- b)、对于没有会话层的询问,NSURLSession对象调用session的代理方法URLSession:task:didReceiveChallenge:completionHandler:去处理询问。如果在程序中提供了session的代理并需要处理身份认证,那么我们必须在任务级别处理身份认证或者提供一个任务级别的处理程序,并明确地在每一个session中调用这些处理程序。session的代理方法URLSession:didReceiveChallenge:completionHandler: 不会在无会话层的询问中调用。
如果上传任务在身份认证时失败、任务数据是以数据流的方式提供,NSURLSession对象会调用 URLSession:task:needNewBodyStream:代理方法。代理对象必须为新的请求提供一个新的
NSInputStream
对象来包装请求实体数据。这方面更多地在前面的“身份认证询问和TLS链验证“章节讲解。当收到了HTTP重定向的响应,NSURLSession对象会调用URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:代理方法。该代理方法调用提供的completionHandler(其传入值可以是已提供的NSURLRequest对象,或者一个新的NSURLRequest对象,或者传入nil)。传入提供的NSURLRequest对象就是遵守重定向,传入新的NSURLRequest对象意思是重定向到一个不同的URL,传入nil是将重定向的响应实体视为有效响应,并将其作为结果返回。
如果遵守了重定向则返回到第4步(身份认证询问处理)。
如果代理没有实现该方法,重定向基于最大重定向数目。
对于通过调用downloadTaskWithResumeData:或者downloadTaskWithResumeData:completionHandler:创建的(重新)下载任务,NSURLSession调用URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:代理方法来处理新的任务对象。
对于数据任务,NSURLSession对象调用代理方法URLSession:dataTask:didReceiveResponse:completionHandler:来决定是否把数据任务转换为下载任务,并调用completion回调来继续接收数据任务的数据或者下载任务的数据。
如果我们选择把数据任务转换为下载任务,
NSURLSession
对象会调用代理方法URLSession:dataTask:didBecomeDownloadTask:方法,并将新的下载任务作为参数传递。在调用之后,不会接收到数据任务的回调,并开始接收下载的回调。如果是由uploadTaskWithStreamedRequest:方法创建的任务,
NSURLSession
调用URLSession:task:needNewBodyStream: 代理方法来提供请求实体数据。在初次将请求实体内容传递给服务器时(如果可用),代理会定期地接收URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:的回调,并告知上传的进度。
在和服务器的传输过程中,任务代理定期地接收回调并报告传输进度。对于下载任务,session调用代理方法URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:来获取成功写入到磁盘中数据的数目;对于数据任务,session调用URLSession:dataTask:didReceiveData:代理方法来获取接收的实际数据块儿。
对于下载任务,在传输过程中如果要暂停下载,调用cancelByProducingResumeData:方法来取消任务。然后如果用户想要重启下载任务,把返回的resumeData传递给downloadTaskWithResumeData:或者downloadTaskWithResumeData:completionHandler:方法去创建一个新的下载任务以继续之前的下载。然后回退到第3步(创建或者重启任务对象)。
对于数据任务,NSURLSession对象调用URLSession:dataTask:willCacheResponse:completionHandler:代理方法,应该由我们app来决定是否需要缓存。如果并没有实现相关方法的话,默认的行为是使用session配置对象中指定的缓存策略。
如果下载任务成功完成,session对象调用代理方法URLSession:downloadTask:didFinishDownloadingToURL: 来定位零时文件的位置。如果我们想要从文件中读取响应数据或者要将该数据移动到沙盒中的文件夹中,那么我们必须要在该代理方法返回之前完成。
当所有任务完成之后,NSURLSession对象调用代理方法URLSession:task:didCompleteWithError:来传递给程序一个error或者nil(成功完成error为nil)。
如果任务失败时大多数app需要重试该请求,直到用户取消下载或者服务器返回了包含该请求永远不可能成功的错误消息。我们在程序中不应该显示地去重试,相反的是,我们应该使用相关的API来确定远程服务器是否可达,并在可达性更改之后创建一个新的请求。
如果下载可被重启,NSError对象的userInfo字典中包含了以NSURLSessionDownloadTaskResumeData为key的值。当创建一个新的下载任务以继续之前的下载时,我们应该在调用downloadTaskWithResumeData: 或者downloadTaskWithResumeData:completionHandler:时把这个值传给这两个方法。
如果任务不能被重启,我们应该创建一个新的下载任务从头开始。
在这两个场景中,如果由于服务端错误导致传输失败,回到第3步(创建和重启任务对象)。
如果响应内容被多部分编码(multipart encoded),session会再次调用
appdelegate
的didReceiveResponse
方法,并且会额外不止一次的调用didReceiveData
。如果发生了这种情况则跳到第7步去处理didReceiveResponse
的调用。如果我们app不再需要session,调用invalidateAndCancel(取消任务)或者finishTasksAndInvalidate(在废弃对象之前完成任务)废弃它。在废弃了该session之后,等到所有的任务被取消或者完成,session会给代理发送一个URLSession:didBecomeInvalidWithError:消息。当在代理方法返回时,session对象与代理的强引用将会断开。
⚠️⚠️⚠️ 重要
当在程序中明确地废弃了session对象之前,session对象都对delegate对象保持了强引用。如果不废弃session的话,那么我们app将会造成内存泄漏。
如果我们取消了一个正在下载的任务,NSURLSession对象会调用URLSession:task:didCompleteWithError:代理方法,它就好像发生了错误一样。