[iOS-Foundation] NSURLSession

一个 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 使用了共享的
    NSURLCacheNSHTTPCookieStorageNSURLCredentialStorage对象。
  • - 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 的值为:
    NSURLAuthenticationMethodNTLMNSURLAuthenticationMethodNegotiate
    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 要在主线程中执行。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容