iOS基础深入补完计划--NSURLSession使用详解(附Demo)

目录

  • 前言
  • API
  • Demo

前言

本文主要是把NSURLSession以及NSURLSessionTask相关的代理方法全部整理了一下。
旨在大体了解在一个iOS网络请求中、一个任务究竟经理了什么。
而我们、又能做些什么。


API

大概的结构长这样


API结构
#pragma mark - NSURLSessionDelegate
#pragma mark 会话总代理
#pragma mark 通知>>session被关闭
//[session invalidateAndCancel]或者[session finishTasksAndInvalidate]
//session被关闭时调用、持有的delegate将被清空
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error {
    NSLog(@"NSURLSessionDelegate:::通知>>session被关闭");
}

#pragma mark 询问>>服务器客户端配合验证--会话级别
/*
 参考:
 https://www.jianshu.com/p/2642e31919e7
 https://www.2cto.com/kf/201604/504149.html
 https://blog.csdn.net/jingcheng345413/article/details/65437649
 NSURLAuthenticationChallenge 类中最重要的一个属性是protectionSpace。
 该属性是一个 NSURLProtectionSpace 的实例,一个NSURLProtectionSpace对象通过属性host、isProxy、port、protocol、proxyType和realm代表了请求验证的服务器端的范围。
 而NSURLProtectionSpace类的authenticationMethod属性则指明了服务端的验证方式,可能的值包括:
 
    challenge.protectionSpace {
        // 默认
        NSURLAuthenticationMethodDefault
        // 基本的 HTTP 验证,通过 NSURLCredential 对象提供用户名和密码。
        NSURLAuthenticationMethodHTTPBasic
        // 类似于基本的 HTTP 验证,摘要会自动生成,同样通过 NSURLCredential 对象提供用户名和密码。
        NSURLAuthenticationMethodHTTPDigest
        // 不会用于 URL Loading System,在通过 web 表单验证时可能用到。
        NSURLAuthenticationMethodHTMLForm
 
        <<<<<***************>>>>>
        //Negotiate(协商,Kerberos or NTLM)
        NSURLAuthenticationMethodNegotiate
        //NTLM(WindowsNT使用的认证方式
        NSURLAuthenticationMethodNTLM
        // 验证客户端的证书
        NSURLAuthenticationMethodClientCertificate
        // 指明客户端要验证服务端提供的证书
        NSURLAuthenticationMethodServerTrust
    }
 
 其中后四个为会话级别验证
 将会优先调用会话级别验证、如果未实现再调用任务界别验证。
 */
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler {
    NSLog(@"NSURLSessionDelegate:::询问>>服务器客户端配合验证--会话级别");
    completionHandler(NSURLSessionAuthChallengePerformDefaultHandling,nil);
}

#pragma mark 通知>>所有后台下载任务全部完成
//必须在backgroundSessionConfiguration 并且在后台完成时才会调用
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
    NSLog(@"NSURLSessionDelegate:::通知>>所有后台下载任务全部完成");
}


#pragma mark - NSURLSessionTaskDelegate
#pragma mark 任务总代理

#pragma mark 通知>>延时任务被调用
/*
    当设置了earliestBeginDate属性
    (需要注意这个属性对于非后台任务并不有效、而且不能保证定时执行、只能保证不会在指定日期之前执行)
    的NSURLSessionTask被延迟调用的、会走这里
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
willBeginDelayedRequest:(NSURLRequest *)request
 completionHandler:(void (^)(NSURLSessionDelayedRequestDisposition disposition, NSURLRequest * _Nullable newRequest))completionHandler {
    NSLog(@"NSURLSessionTaskDelegate:::通知>>延时任务被调用");
    /*
        typedef NS_ENUM(NSInteger, NSURLSessionDelayedRequestDisposition) {
        NSURLSessionDelayedRequestContinueLoading = 0,  //使用原始请求、参数忽略
        NSURLSessionDelayedRequestUseNewRequest = 1,    //使用新请求
        NSURLSessionDelayedRequestCancel = 2,   //取消任务、参数忽略
        }
     */
    completionHandler(NSURLSessionDelayedRequestContinueLoading,request);
}


#pragma mark 通知>>网络受限导致任务进入等待
/*
    如果NSURLSessionConfiguration的waitsForConnectivity属性为true
    并且由于网络不通(并不是并发受限)没有被立即发出时
    此方法最多只能在每个任务中调用一次、并且仅在连接最初不可用时调用。
    它永远不会被调用后台会话,因为这些会话会忽略waitsForConnectivity。
 
 */
- (void)URLSession:(NSURLSession *)session taskIsWaitingForConnectivity:(NSURLSessionTask *)task {
    NSLog(@"NSURLSessionTaskDelegate:::通知>>网络受限导致任务进入等待");
}

#pragma mark 准备开始请求、询问是否重定向
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
        newRequest:(NSURLRequest *)request
 completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler {
    NSLog(@"NSURLSessionTaskDelegate:::询问>>是否重定向");
    completionHandler(request);
}

#pragma mark 询问>>服务器需要客户端配合验证--任务级别
//会话级别除非未实现对应代理、否则不会调用任务级别验证方法
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler {
    NSLog(@"NSURLSessionTaskDelegate:::询问>>服务器需要客户端配合验证--任务级别");
    NSURLCredential * cre =[NSURLCredential credentialWithUser:@"kirito_song" password:@"psw" persistence:NSURLCredentialPersistenceNone];
    completionHandler(NSURLSessionAuthChallengeUseCredential,cre);
}

#pragma mark 询问>>流任务的方式上传--需要客户端提供数据源
/* 当任务需要新的请求主体流发送到远程服务器时,告诉委托。
 这种委托方法在两种情况下被调用:
 1、如果使用uploadTaskWithStreamedRequest创建任务,则提供初始请求正文流:
 2、如果任务因身份验证质询或其他可恢复的服务器错误需要重新发送包含正文流的请求,则提供替换请求正文流。
 注:如果代码使用文件URL或NSData对象提供请求主体,则不需要实现此功能。
 */

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
 needNewBodyStream:(void (^)(NSInputStream * _Nullable bodyStream))completionHandler {
    NSLog(@"NSURLSessionTaskDelegate:::询问>>数据流的方式上传--需要客户端提供数据源");
}

#pragma mark 通知>>上传进度
/* 定期通知代理向服务器发送主体内容的进度。*/
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
   didSendBodyData:(int64_t)bytesSent
    totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
    NSLog(@"NSURLSessionTaskDelegate:::通知>>上传进度");
}



#pragma mark 通知>>任务信息收集完成
/*
 对发送请求/DNS查询/TLS握手/请求响应等各种环节时间上的统计. 更易于我们检测, 分析我们App的请求缓慢到底是发生在哪个环节, 并对此进行优化提升我们APP的性能.
 
 NSURLSessionTaskMetrics对象与NSURLSessionTask对象一一对应. 每个NSURLSessionTaskMetrics对象内有3个属性 :
 
 taskInterval : task从开始到结束总共用的时间
 redirectCount : task重定向的次数
 transactionMetrics : 一个task从发出请求到收到数据过程中派生出的每个子请求, 它是一个装着许多NSURLSessionTaskTransactionMetrics对象的数组. 每个对象都代表下图的一个子过程
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics {
    NSLog(@"NSURLSessionTaskDelegate:::通知>>任务信息收集完成");
    NSLog(@"::::::::::::相关讯息::::::::::::\n总时间:%@\n,重定向次数:%zd\n,派生的子请求:%zd",metrics.taskInterval,metrics.redirectCount,metrics.transactionMetrics.count);
}

#pragma mark 通知>>任务完成
//无论成功、失败或者取消
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error {
    NSLog(@"NSURLSessionTaskDelegate:::通知>>任务完成");
}

#pragma mark - NSURLSessionDataDelegate
#pragma mark 数据任务代理
#pragma mark 通知>>服务器返回响应头
#pragma mark 询问>>下一步操作
//服务器返回响应头、询问下一步操作(取消操作、普通传输、下载、数据流传输)
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
    NSLog(@"NSURLSessionDataDelegate:::通知>>服务器返回响应头。询问>>下一步操作");
    completionHandler(NSURLSessionResponseAllow);
//    completionHandler(NSURLSessionResponseBecomeDownload);
//    completionHandler(NSURLSessionResponseBecomeStream);
}

#pragma mark 通知>>数据任务已更改为下载任务
//你可以通过上面的 completionHandler(NSURLSessionResponseBecomeDownload);进行测试
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask {
    NSLog(@"NSURLSessionDataDelegate:::通知>>数据任务已更改为下载任务");
}

#pragma mark 通知>>数据任务已更改为流任务
//你可以通过上面的 completionHandler(NSURLSessionResponseBecomeStream);进行测试
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didBecomeStreamTask:(NSURLSessionStreamTask *)streamTask {
    NSLog(@"NSURLSessionDataDelegate:::通知>>数据任务已更改为下载任务");
}

#pragma mark 通知>>服务器成功返回数据
//已经收到了一些(大数据可能多次调用)数据
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data {
    NSLog(@"NSURLSessionDataDelegate:::通知>>服务器成功返回数据");
}

#pragma mark 询问>>是否把Response存储到Cache中
//任务是否应将响应存储在缓存中
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
 willCacheResponse:(NSCachedURLResponse *)proposedResponse
 completionHandler:(void (^)(NSCachedURLResponse * _Nullable cachedResponse))completionHandler {
    NSLog(@"NSURLSessionDataDelegate:::询问>>是否把Response存储到Cache中");
    NSCachedURLResponse * res = [[NSCachedURLResponse alloc]initWithResponse:proposedResponse.response data:proposedResponse.data userInfo:nil storagePolicy:NSURLCacheStorageNotAllowed];
    completionHandler(res);
}


#pragma mark - NSURLSessionDownloadDelegate
#pragma mark 下载任务代理
#pragma mark 通知>>下载任务已经完成
//location 临时文件的位置url 需要手动移动文件至需要保存的目录
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location {
    NSLog(@"NSURLSessionDownloadDelegate:::通知>>下载任务已经完成");
}

#pragma mark 通知>>下载任务进度
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
    
    NSLog(@"NSURLSessionTaskDelegate:::通知>>下载任务进度");
    
}

#pragma mark 通知>>下载任务已经恢复下载
//filrOffest 已经下载的文件大小  expectedTotalBytes预期总大小
/*
    你可以通过 [session downloadTaskWithResumeData:resumeData]之类的方法来重新恢复一个下载任务
    resumeData在下载任务失败的时候会通过error.userInfo[NSURLSessionDownloadTaskResumeData]来返回以供保存
 */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
 didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes {
    NSLog(@"NSURLSessionTaskDelegate:::通知>>下载任务已经恢复下载");
}


#pragma mark - NSURLSessionStreamDelegate
#pragma mark 流任务代理
#pragma mark 通知>>数据流的连接中读数据的一边已经关闭
- (void)URLSession:(NSURLSession *)session readClosedForStreamTask:(NSURLSessionStreamTask *)streamTask {
    NSLog(@"NSURLSessionStreamDelegate:::通知>>数据流的连接中读数据的一边已经关闭");
}

#pragma mark 通知>>数据流的连接中写数据的一边已经关闭
- (void)URLSession:(NSURLSession *)session writeClosedForStreamTask:(NSURLSessionStreamTask *)streamTask {
    NSLog(@"NSURLSessionStreamDelegate:::通知>>数据流的连接中写数据的一边已经关闭");
}

#pragma mark 通知>>系统已经发现了一个更好的连接主机的路径
- (void)URLSession:(NSURLSession *)session betterRouteDiscoveredForStreamTask:(NSURLSessionStreamTask *)streamTask {
    NSLog(@"NSURLSessionStreamDelegate:::通知>>系统已经发现了一个更好的连接主机的路径");
}

#pragma mark 通知>>流任务已完成
- (void)URLSession:(NSURLSession *)session streamTask:(NSURLSessionStreamTask *)streamTask
didBecomeInputStream:(NSInputStream *)inputStream
      outputStream:(NSOutputStream *)outputStream {
    NSLog(@"NSURLSessionStreamDelegate:::通知>>流任务已完成");
}


Demo

简单的写了几个具体用法、以更直观的观测代理方法的运行以及顺序


文本请求.gif
图片请求.gif
下载任务&&断点续传.gif

Demo可以自取


最后

本文主要是自己的学习与总结。如果文内存在纰漏、万望留言斧正。如果不吝赐教小弟更加感谢。


参考

NSURLSession简介
NSURLSession使用注意事项
NSURLSession使用说明及后台工作流程分析
iOS使用NSURLSession进行下载(包括后台下载,断点下载)
[iOS-Foundation] Networking Authentication
iOS网络请求认证挑战
网络请求之NSURLSession(api篇)
深入了解NSURLSession
缓存

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

推荐阅读更多精彩内容