一个 NSURLSession 对象代表了一个 session(会话),这个 session 可以类比为浏览器的一个 tab 或者负责某一类请求例如后台下载的环境,它创建并管理着一组 NSURLSessionTask,每个NSURLSessionTask
对象则对应了一个特定的请求。
当创建一个NSURLSession
对象时,需要一个提供配置信息的
NSURLSessionConfiguration 对象,这个配置信息对象定义了同一域名的最大连接数、移动网络下是否可请求、请求的缓存和 cookie 保存策略等行为,session 中的所有 task 共用这个配置。
注意区别于 HTTP 协议中的 session 概念。HTTP 本身是无状态的协议,请求之间是没有联系的,所以 web 应用为了保存用户登录状态,返回用户信息,就需要用到会话技术。HTTP 会话的原理是,当需要开启一个会话时,服务器端会生成一个 session ID 并保存到客户端的 cookie 中,之后客户端的请求带有的 cookie 里就有了其对应会话的 session ID,服务器通过这个 session ID 就可以去获取到保存在服务器端的对应的会话信息,并做相应处理了。session 的信息可以保存在服务器的临时文件中,也可以用 redis 来保存 session 信息。
创建 session
-
sharedSession
,共享的单例 session 对象,不需要通过 configuration 和 delegate 来创建,只提供获取资源到内存中的支持。因为 session 对象的 delegate 属性为只读,所以无法设置sharedSession
的delegate。shared session 使用了共享的
NSURLCache
、NSHTTPCookieStorage
和NSURLCredentialStorage
对象。 -
- sessionWithConfiguration:
,根据不同的NSURLSessionConfiguration
对象(default、ephemeral 或 background)创建 session 对象。 -
- sessionWithConfiguration:delegate:delegateQueue:
,根据 configuration 对象创建 session 的同时,设置 session 对象的 delegate 和 delegate queue。
session 相关属性
-
configuration
,session 的 configuration 对象的 copy,修改这个 configuration 对象并不会影响 session,可用于创建新的 session。 -
delegate
,创建 session 时传入的 delegate,session 对delegate 强引用,只有当 session 执行 invalidate 相关方法后,才会释放引用。 -
delegateQueue
,创建 session 时传入的 delegateQueue,所有的代理方法和 block 回调都会在这个 queue 中执行。 -
sessionDescription
,可给 session 设置一个描述,用于调试或界面显示。
创建 task
根据请求的不同需求,session 可以创建 data task、download task、upload task 和 stream task。
// data task
- dataTaskWithURL:
- dataTaskWithURL:completionHandler:
- dataTaskWithRequest:
- dataTaskWithRequest:completionHandler:
// download task
- downloadTaskWithURL:
- downloadTaskWithURL:completionHandler:
- downloadTaskWithRequest:
- downloadTaskWithRequest:completionHandler:
- downloadTaskWithResumeData:
- downloadTaskWithResumeData:completionHandler:
// upload task
- uploadTaskWithRequest:fromData:
- uploadTaskWithRequest:fromData:completionHandler:
- uploadTaskWithRequest:fromFile:
- uploadTaskWithRequest:fromFile:completionHandler:
- uploadTaskWithStreamedRequest:
// session task
- streamTaskWithHostName:port:
- streamTaskWithNetService:
如果传入了 completionHandler 参数,那么 delegate 中返回数据、返回响应、请求完成等方法都不会再调用,但其他 delegate 方法还是会调用,如证书鉴定等。
调用 completionHandler 会传入3个参数,第一个参数是返回的数据,data task 和 upload task 为NSData
类型,download task 为临时文件保存路径的NSURL
对象,该文件在回调执行完后会被删除,所以要在回调中做相应的持久化处理。后两个参数是NSURLResponce
类型的 response 和NSError
类型的 error。
对于 HTTP 协议来说,无论服务器返回的响应 code 是成功还是失败,以 task 的角度看,这个请求 task 是成功的,data 中会包含返回的内容,response 代表了响应的元信息,error 则为 nil,如果发生客户端相关错误导致 task 请求失败,error 中则会包含相关信息。如果创建 task 时传入的是NSURLRequest
对象而不是NSURL
对象,那么 task 除了请求 request 中的 URL 指定的资源,相关的请求行为,如请求方法(GET or POST)、缓存策略、超时时间、请求体内容等,也都会根据 request 对象设置,而这可能会覆盖 configuration 中指定的某些行为。
另外 download task 还可以根据下载中断时的 resume data 创建。调用 download task 的- cancelByProducingResumeData:
方法会在回调中返回 resume data,另一种可能获得 resume data 的情况是,在 download task 失败时,返回的 error 的 userInfo 里 keyNSURLSessionDownloadTaskResumeData
对应保存了 resume data。
而 upload task 的创建必须传入NSURLRequest
对象,上传的内容可以通过参数传入NSData
对象或代表文件路径的NSURL
对象。对于
- uploadTaskWithStreamedRequest:
方法,则必须实现代理方法
URLSession:task:needNewBodyStream:
用以提供上传内容。
管理 session
session 和 task 的实例可以不需要应用来维护,系统会维护创建的 session 实例,而 task 实例由创建它的 session 维护,当一个 task 执行完成,如果没有其他引用,则会被销毁,当 session 调用了 invalidate 方法,如果没有其他引用,则会被销毁。
- finishTasksAndInvalidate
- invalidateAndCancel
当对 session invalidate 后,就不能再创建新的 task 了,两个方法的不同之处是,- finishTasksAndInvalidate
会等到正在执行的 task 执行完成,调用完所有回调或 delegate 后,释放对 delegate 的强引用,而- invalidateAndCancel
方法则是直接取消所有正在执行的 task。
- flushWithCompletionHandler:
将内存中的 cookie、证书等写到硬盘,之后的请求会使用新的 TCP 连接,传入的 completionHandler 在上述操作完成后执行。
- resetWithCompletionHandler:
清空所有的 cookie、缓存、证书等,传入的 completionHandler 在上述操作完成后执行。
- (void)getTasksWithCompletionHandler:(void (^)(NSArray<NSURLSessionDataTask *> *dataTasks, NSArray<NSURLSessionUploadTask *> *uploadTasks, NSArray<NSURLSessionDownloadTask *> *downloadTasks))completionHandler;
- (void)getAllTasksWithCompletionHandler:(void (^)(NSArray<__kindof NSURLSessionTask *> *tasks))completionHandler;
获取 session 中的 task,在获取完 task 列表后会执行传入的 completionHandler 参数,而 task 列表则作为 block 的参数传入。
Delegate
通过 session 的 delegate 可以在请求的整个生命周期执行许多自定义的行为。若使用 session 时不提供 delegate,session 对象则使用一个系统提供的 delegate,在这种情况下,创建 task 时就必须传入处理回调的 block 参数,否则无法获得返回的数据了。
@protocol NSURLSessionDelegate <NSObject>
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error;
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler;
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session;
@end
@protocol NSURLSessionTaskDelegate <NSURLSessionDelegate>
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * __nullable))completionHandler;
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler;
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task needNewBodyStream:(void (^)(NSInputStream * __nullable bodyStream))completionHandler;
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend;
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error;
@end
@protocol NSURLSessionDataDelegate <NSURLSessionTaskDelegate>
@optional
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler;
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask;
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeStreamTask:(NSURLSessionStreamTask *)streamTask;
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data;
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse * __nullable cachedResponse))completionHandler;
@end
@protocol NSURLSessionDownloadDelegate <NSURLSessionTaskDelegate>
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location;
@optional
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes;
@end
@protocol NSURLSessionStreamDelegate <NSURLSessionTaskDelegate>
@optional
- (void)URLSession:(NSURLSession *)session readClosedForStreamTask:(NSURLSessionStreamTask *)streamTask;
- (void)URLSession:(NSURLSession *)session writeClosedForStreamTask:(NSURLSessionStreamTask *)streamTask;
- (void)URLSession:(NSURLSession *)session betterRouteDiscoveredForStreamTask:(NSURLSessionStreamTask *)streamTask;
- (void)URLSession:(NSURLSession *)session streamTask:(NSURLSessionStreamTask *)streamTask didBecomeInputStream:(NSInputStream *)inputStream outputStream:(NSOutputStream *)outputStream;
@end
- 当在 SSL 握手阶段,如果服务器要求验证客户端身份或向客户端提供其证书用于验证时,则会调用
URLSession:didReceiveChallenge:completionHandler:
或
URLSession:task:didReceiveChallenge:completionHandler:
方法。传入的参数除了对应的 session 和 task,还有NSURLAuthenticationChallenge
对象,它封装了服务端对客户端的验证请求,根据它的 protectionSpace 属性的
authenticationMethod,可以知道服务端要通过哪种方式验证客户端或是要求客户端验证服务端的证书。在代理方法中要执行传入的
completionHandler Block,这个回调接收两个参数:
NSURLSessionAuthChallengeDisposition
类型的 disposition,描述了如何处理验证请求,NSURLCredential
对象,当处理方式需要提供证书时,传入该参数。当 authenticationMethod 的值为:
NSURLAuthenticationMethodNTLM
、NSURLAuthenticationMethodNegotiate
、
NSURLAuthenticationMethodClientCertificate
和
NSURLAuthenticationMethodServerTrust
时,系统会先尝试调用 session 级的处理方法,若 session 级未实现,则尝试调用 task 级的处理方法,而其他情况则是直接调用 task 级的处理方法,无论 session 级方法是否实现。 - 当使用
- uploadTaskWithStreamedRequest:
方法创建的 task 发起请求时,必须实现代理方法URLSession:task:needNewBodyStream:
,以提供请求需要的 stream。因为从输入流读数据是不可逆的,所以在上传失败时,可能会重新调用该方法读取流。在方法中执行 block completionHandler 参数,向其传入
NSInputStream
对象。 - 当执行 upload task 时,系统会定期的调用
URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:
方法,报告上传请求体的进度。 - 当响应为重定向到其他请求时,系统会调用代理方法
URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:
,在方法中要执行 block 参数 completionHandler,可向其传入重定向的新的NSURLRequest
对象,那么会正常执行重定向请求,也可以传入 nil,则不执行重定向请求并以当前响应体作为重定向后的响应。 - 当执行以方法
- downloadTaskWithResumeData:
或
- downloadTaskWithResumeData:completionHandler:
创建的 download task 时,会调用代理方法
URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:
- 当 data task 收到响应时,会调用代理方法
URLSession:dataTask:didReceiveResponse:completionHandler:
,在方法中要执行 block 参数 completionHandler,向其传入
NSURLSessionResponseDisposition
枚举类型参数,指明继续正常返回响应体,还是取消请求,或者将 task 转变为 download task。支持
multipart/x-mixed-replace
时,传入NSURLSessionResponseBecomeDownload
,将 task 转换为 download task 后,会调用代理方法
URLSession:dataTask:didBecomeDownloadTask:
- 当从服务端接收数据时,系统会调用相应的代理方法,定期提供已接收到的数据,对于 data task,会调用方法
URLSession:dataTask:didReceiveData:
,而 download task 则会调用方法
URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:
- 对于 data task 或 upload task,在收到所有数据后,可能会调用方法
URLSession:dataTask:willCacheResponse:completionHandler:
,提供给应用决定是否缓存响应的机会。方法中要执行 block 参数 completionHandler,如果缓存则传入一个NSCachedURLResponse
对象,若不想缓存可传入 nil。当返回的响应为缓存中的时候,则不调用该方法。 - 当 task 执行完成后,会调用代理方法
URLSession:task:didCompleteWithError:
,如果 task 失败,error 参数为包含具体信息的NSError
对象,若成功,error 则为 nil。对于 HTTP 协议来说,服务器返回的响应 code 为失败时,不属于 task 执行失败的情况,以 task 的角度看,这个请求 task 是成功执行的,data 中会包含返回的内容。当 download task 失败时,在 error 对象的 userInfo 中可以根据 key
NSURLSessionDownloadTaskResumeData
获得已下载的数据,之后可用于创建从断点处继续下载的 task。当 download task 成功执行完成后,还会执行代理方法URLSession:downloadTask:didFinishDownloadingToURL:
,这个方法是
NSURLSessionDownloadDelegate
协议中必须实现的,方法参数传入一个下载文件的临时保存路径,应用需要读取文件的内容或将文件移到其他地方,因为代理方法执行结束后,该路径下的文件会被删除,注意,如果读取文件内容的话,则要在单独的线程中进行,否则可能会造成页面卡死,影响用户体验。 - 当对 session 对象执行了 invalidate 的相关方法后,会调用代理方法
URLSession:didBecomeInvalidWithError:
。之后 session 会释放对代理对象的强引用。
拷贝策略
当拷贝一个 session 对象或 task 对象时,并不会创建新的实例,而是返回该对象本身。
后台 session
当挂起的应用执行的后台传输(下载、上传)完成或者发生错误以及需要身份验证时,系统会运行应用并调用应用代理的
- application:handleEventsForBackgroundURLSession:completionHandler:
方法。如果传输完成可以执行一些更新界面的操作,如果没有成功,则可以根据 identifier 参数重新创建 background session 继续执行。在该方法中应使用实例变量强引用 block 参数 completionHandler,在上述相关操作(如重新关联 session)完成后,系统会调用 session 的代理方法
URLSessionDidFinishEventsForBackgroundURLSession:
,此时在该代理方法中运行之前应用代理维持的 block,以通知系统应用已完成处理工作,要注意的是,该 completionHandler 要在主线程中执行。