[iOS-Foundation] NSURLSession

2016-10-11  本文已影响673人  水止云起

一个 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

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

拷贝策略

当拷贝一个 session 对象或 task 对象时,并不会创建新的实例,而是返回该对象本身。

后台 session

当挂起的应用执行的后台传输(下载、上传)完成或者发生错误以及需要身份验证时,系统会运行应用并调用应用代理的
- application:handleEventsForBackgroundURLSession:completionHandler:方法。如果传输完成可以执行一些更新界面的操作,如果没有成功,则可以根据 identifier 参数重新创建 background session 继续执行。在该方法中应使用实例变量强引用 block 参数 completionHandler,在上述相关操作(如重新关联 session)完成后,系统会调用 session 的代理方法
URLSessionDidFinishEventsForBackgroundURLSession:,此时在该代理方法中运行之前应用代理维持的 block,以通知系统应用已完成处理工作,要注意的是,该 completionHandler 要在主线程中执行。

上一篇 下一篇

猜你喜欢

热点阅读