URLSession的生命周期
你可以有两种方法使用NSURLSession的api:一种是用系统提供的代理,一种是用自定义的代理。一般情况下,下列情况必须使用你自定义的代理:
1.当你的app不再运行的时候,在后台下载或者上传数据
2.执行自定义的认证
3.执行自定义的SSL证书认证
4.决定一个传输是否应该下载到磁盘还是根据服务器返回的MIME类型进行展示
5.用流作为请求体上传数据
6.编程限制缓存
7.编程限制http重定向
系统提供代理的URLSession生命周期
如果你使用的URLSession类没有提供代理,那么系统就会用系统提供的代理来帮助你处理细节。以下是一些你的app必须要做的方法调用和应用接收到的完成处理块调用。
1.创建一个会话配置(session configuration)。对于后台会话,这个配置必须包含一个唯一的标识符。保存那个标识符,并且当app崩溃,终止或者是暂停时,用这个标识符来重新关联会话。
2.创建一个会话(session)。指定一个配置对象,和一个nil代理
3.在一个会话里创建任务对象,每一个任务代表着一个资源请求。
每一个任务都是从暂停状态开始的。在你的app调用任务的resume方法后,它开始下载特定的资源。任务对象是NSURLSessionTask的子类:NSURLSessionDataTask,NSURLSessionUploadTask,NSURLSessionDownloadTask。这些对象和NSURLConnection的对象是相似的,但是前者给予你更多的控制和统一的代理模型。
尽管你的app可以在一个会话里添加多个任务,但是为简单起见,接下来的步骤主要表述单个任务的生命周期。
note:如果你使用的会话类没有提供代理,那么在创建任务时必须使用带有完成处理块的方法。否则就不能获得数据。
4.对于一个下载任务。在从服务器传输的过程中,如果用户告诉app暂停下载,那么就会调用cancelByProducingResumeData:方法来取消任务。之后,传递这个返回的恢复数据给downloadTaskWithResumeData:或者downloadTaskWithResumeData:completionHandler:方法来创建一个新的下载任务来继续下载。
5.当任务完成,NSURLSession对象调用任务的完成处理块。
note:NSURLSession不会报告服务器端的错误,它只会接收客户端的错误。服务器端的错误可以通过NSHTTPResponse对象中的HTTP状态码来获得。
6.当你的app不再需要会话,可以通过invalidateAndCancel(直接取消所有未完成的任务然后无效会话)或者finishTasksAndInvalidate(等待所有未完成的任务结束之后再无效会话)方法来无效会话。
带有自定义代理URLSession的生命周期
你可以经常不提供代理来使用NSURLSession,但是,如果你要进行后台下载或上传,或者你需要处理认证或者用一种不默认的行为缓存,你必须提供一个代理来遵守会话代理协议,一个或多个任务代理协议或者这些协议的一些组合。这个代理服务于很多情况:
1.当用在下载任务时,会话对象可以使用代理来为app提供下载数据的文件URL。所有的后台下载和上传,都需要代理来完成。这些代理必须提供所有NSURLSessionDownloadDelegate协议中的方法。
2.代理可以处理特定的认证。
3.代理可以提供用流做请求体,上传数据到服务器。
4.代理可以决定是否支持HTTP重定向。
5.会话对象使用代理来提供给app每个传输的状态。数据任务代理既接收一个初始的调用(在这个调用中你可以将请求转化为一个下载任务),也可以接受一个后来的调用(提供从远程服务器获得的数据段)。
6.代理是一个会话可以告诉你传输什么时候结束的一种方式。
使用自定义代理的会话的生命周期更加复杂。以下是一些基本的函数调用和代理调用:
1.创建一个会话配置(session configuration)。对于后台会话,这个配置必须包含一个唯一的标识符,保存这个标识符,当app崩溃,暂停或终止时,可以用这个标识符重新关联会话。
2.创建会话,指定会话的配置对象和代理。
3.创建任务。
4.如果远程服务器但会一个状态码指明需要认证,而且这个认证需要一个连接层级的挑战(例如SSL客户端证书),会话调用一个认证挑战代理方法。
(1)对于会话级别的挑战-NSURLAuthenticationMethodNTLM,NSURLAuthenticationMethodNegotiate,NSURLAuthenticationMethodClientCertificate,或者NSURLAuthenticationMethodServerTrust,会话对象调用会话代理的URLSession:didReceiveChallenge:completionHandler:方法。如果你的app没有提供会话代理方法,会话对象会调用任务代理的URLSession:task:didReceiveChallenge:completionHandler:方法来处理挑战。
(2)对于非会话级别的挑战,会话对象调用任务代理的URLSession:task:didReceiveChallenge:completionHandler:方法来处理挑战。如果你的app提供一个会话代理并且你需要处理认证,那么你必须处理这个认证在任务级别或者提供一个任务级别的处理通过直接调用每个任务的处理块。会话代理的URLSession:didReceiveChallenge:completionHandler:方法对于非会话级别的挑战不会被调用。
如果一个上传任务的认证失败,且任务数据来自流,会话对象会调用代理的URLSession:task:needNewBodyStream:代理方法。那么这个代理必须提供一个新的NSInputStream对象来提供新请求的请求体数据。
5.当接收到一个HTTP重定向响应,会话对象会调用代理的URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler方法。这个代理方法调用提供的完成处理块通过提供的NSURLRequest对象(遵循重定向),或者一个新的NSURLRequest对象(重定向到一个不同的URL),或者nil(对待重定向的响应体作为一个有效的响应并且作为结果返回)
(1)如果遵从了一个重定向,返回第四步。
(2)如果代理没有实现这个方法,重定向会遵循重定向的最大数。
6.对于一个通过调用downloadTaskWithResumeData:或者downloadTaskWithResumeData:completionHandler:创建的下载任务,会话会用一个新的任务对象调用代理的URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:方法。
7.对于一个数据任务,会话对象调用代理的URLSession:dataTask:didReceiveResponse:completionHandler:方法。决定是否将数据任务转换为下载任务,然后调用完成回调来继续接收数据或者下载数据。
如果你的app选择转换数据任务为下载任务,会话将会调用代理的URLSession:dataTask:didBecomeDownloadTask:方法,用一个新的下载任务作为参数。这个方法调用之后,代理不再接收来自数据任务的回调,开始接收来自下载任务的回调。
8.如果任务是通过uploadTaskWithStreamedRequest:方法创建的,会话调用代理的URLSession:task:needNewBodyStream:方法来提供数据体。
9.在向服务器上传数据时,代理周期性的接收URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:回调来报告上传进度。
10.在从服务器传输的过程中,任务代理周期性的接收回调来报告传输的进度。对于一个下载任务,会话调用代理的URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:方法将大量字节写入磁盘。对于一个数据任务,会话调用代理的URLSession:dataTask:didReceiveData:方法附带接收到的真正数据段。
对于一个下载任务,在从服务器传输过程中,如果用户告诉app暂停下载,则会通过调用cancelByProducingResumeData:方法来取消任务。之后,传递这个返回的恢复数据给downloadTaskWithResumeData:或者downloadTaskWithResumeData:completionHandler:方法来创建一个新的下载任务来继续下载。然后返回到第三步(创建并且恢复任务对象)。
11.对于一个数据任务,会话对象调用代理的URLSession:dataTask:willCacheResponse:completionHandler:方法。你的app应该决定是否容许缓存。如果你不实现这个方法,默认的行为是使用在会话配置对象中指定的缓存策略。
12.如果下载任务成功完成,那么会话对象调用任务的URLSession:downloadTask:didFinishDownloadingToURL:方法来提供一个临时的文件存储位置。在这个代理方法返回之前,你的app可以从这个文件读取数据,也可以将数据转移到app沙河目录里的一个永久位置。
13.当任何任务完成时,会话对象都会调用代理的URLSession:task:didCompleteWithError:方法,传入的参数可以是一个错误对象或者是nil(任务成功完成)。
如果任务失败,大多数app会重新尝试请求,直到用户取消下载或者服务器返回错误说明请求永远都不会成功。然而你的app不应该立即重新尝试请求,而应该使用可达性api来判断服务器是否可达。并且当app接收到可达性已经改变的通知时,应该创建一个新的请求。
如果下载任务可以被恢复,那么NSError对象的userinfo字典里会包含对应键为NSURLSessionDownloadTaskResumeData的值。你的app应该传递这个值到downloadTaskWithResumeData:或者downloadTaskWithResumeData:completionHandler:来创建一个新的下载任务来继续已存在的下载。
如果任务不能被恢复,你的app应该创建一个新的下载任务,并且从最开始重启事务。
其他的情况,如果因为任何原因除了服务器错误而失败,回到第三步。(创建并恢复任务对象)
14.如果响应是混合编码的,会话可能会再次调用代理的didReceiveResponse方法,随后还会有0次或者更多次额外的didReceiveData的调用。如果这种情况发生了,回到第7步。(处理didReceiveResponse调用)。
15.当你的app不再需要会话,可以通过invalidateAndCancel(直接取消所有未完成的任务然后无效会话)或者finishTasksAndInvalidate(等待所有未完成的任务结束之后再无效会话)方法来无效会话。
在无效会话之后,当所有的未完成任务已经取消或者完成,会话会调用代理的URLSession:didBecomeInvalidWithError:方法。当代理方法返回,会话会销毁对代理的强引用。
note:会话对象对代理保持强引用知道你的app明确的无效会话。如果你不无效会话,你的app会内存泄漏。
如果你的app取消一个进程内的下载,会话对象会调用代理的URLSession:task:didCompleteWithError:Method好像一个错误发生。