深入了解 NSURLSession

前言

本文主要是结合官方文档, 挖掘NSURLSession的类层次结构及其联系, 总结出关于NSURLSession的一些关键点及其用法.

关于NSURLSession为什么能取代NSURLConnection, 其优势是什么, 及其NSURLSession API的概述, 见
关于ATS, HTTP/2, 以及iOS9 NSURLSession新特性 : sharedCookies, streamTask和taskMetrics, 见

以上两篇文章都是我看wwdc视频然后总结出来的文章, 大家感兴趣的可以先了解了解. 如果不想知道那么多, 只想知道怎么用NSURLSession, 那就直接看本文的正文.

*了解URL Loading System

目录



NSURLSession


NSURLSession

  • 支持data, ftp, http(s)协议, 同时支持代理服务器和socks网关.
  • 支持http/1.1, http/2, spdy协议, 但同时需要服务器支持ALPN和NPN.
ALPN与NPN
  • ALPN(Application Layer Protocol Negotiation,应用层协议协商)
  • NPN(Next Protocol Negotiation,下一代协议协商)

NPN是服务端发送它支持的HTTP协议列表, 供客户端选择; 而ALPN则相反, 由客户端发送它支持的HTTP协议列表, 供服务端选择. 如果缺少NPN/ALPN其中一个, 则无法使用HTTP/2通信. 具体请见为什么我们应该尽快支持 ALPN.

NSURLSession相关类为 :

  • NSURLSession
  • NSURLSessionConfiguration
  • NSURLSessionDelegate
  • NSURLSessionTask
  • NSURLSessionTaskMetrics
  • NSURLSessionTaskTransactionMetrics

他们相互的关系如下 :

NSURLSession

session分为 :

  • 全局共享单例session : NSURLSession sharedSession, 有一定的局限性
  • 自定义session : 自定义配置文件, 设置代理, 大部分时间我们都是用这个
  • 后台session : 也是自定义session的一种, 只是他专门用于做后台上传/下载任务

session为哪一种类型完全由其内部的Configuration而定.

NSURLSessionConfiguration

配置分为 :

  • defaultSessionConfiguration : 系统默认
  • ephemeralSessionConfiguration : 仅内存缓存, 不做磁盘缓存的配置
  • backgroundSessionConfiguration : 这里需要指定一个identifier, identifier用来后台重连session对象. (做后台上传/下载就是这个config)

另外, 我们还可以给Configuration对象再自定义一些属性, 例如每端口的最大并发HTTP请求数目, 以及是否允许蜂窝网络, 请求缓存策略, 请求超时, cookies/证书存储策略等等

NSURLSessionDelegate

session管理的一组tasks共享一个代理, 不想实现代理方法时, 代理传nil即可.
代理协议分为 :

  • NSURLSessionDelegate : session-level的代理方法
  • NSURLSessionTaskDelegate : task-level面向all的代理方法
  • NSURLSessionDataDelegate : task-level面向data和upload的代理方法
  • NSURLSessionDownloadDelegate : task-level的面向download的代理方法
  • NSURLSessionStreamDelegate : task-level的面向stream的代理方法
NSURLSessionTask

session task类型分为 :

  • NSURLSessionTask : Task的抽象基类
  • NSURLSessionDataTask : 以NSData的形式接收一个URLRequest的内容
  • NSURLSessionUploadTask : 上传NSData或者本地磁盘中的文件, 完成后以NSData的形式接收一个URLRequest的响应
  • NSURLSessionDownloadTask : 下载完成后返回临时文件在本地磁盘的URL路径
  • NSURLSessionStreamTask : 用于建立一个TCP/IP连接
NSURLSessionTaskMetrics 和 NSURLSessionTaskTransactionMetrics

对发送请求/DNS查询/TLS握手/请求响应等各种环节时间上的统计. 更易于我们检测, 分析我们App的请求缓慢到底是发生在哪个环节, 并对此进行优化提升我们APP的性能.

NSURLSessionTaskMetrics对象与NSURLSessionTask对象一一对应. 每个NSURLSessionTaskMetrics对象内有3个属性 :

  • taskInterval : task从开始到结束总共用的时间
  • redirectCount : task重定向的次数
  • transactionMetrics : 一个task从发出请求到收到数据过程中派生出的每个子请求, 它是一个装着许多NSURLSessionTaskTransactionMetrics对象的数组. 每个对象都代表下图的一个子过程

API很简单, 就一个方法 : - (void)URLSession: task: didFinishCollectingMetrics:, 当收集完成的时候就会调用该方法.

身份验证和自定义TLS


  1. 当一个服务器请求身份验证或TLS握手期间需要提供证书的话, URLSession会调用他的代理方法URLSession:didReceiveChallenge:completionHandler:去处理.
  2. 如果你没有实现该代理方法, URLSession就会这么做 :
    • 使用身份认证信息作为请求URL的一部分(如果可用的话)
    • 在用户的keychain中查找网络密码和证书(in macOS), 在app的keychain中查找(in iOS)
  3. 如果证书还是不可用或服务器拒绝该证书, 就会继续缺少身份认证的连接.
    • 对于HTTP(S)连接, 请求失败并返回一个状态码, 可能会提供一些替代的内容, 例如一个私人网站的公共网页.
    • 对于其他URL类型(如FTP等), 则连接请求失败, 直接返回错误信息

App Transport Security


从iOS9开始支持ATS, 且默认ATS只支持发送HTTPS请求, 不允许发送不安全的HTTP请求. 如果用户需要发送HTTP请求需要在info.plist中配置一些东西.

详情在文章开头的iOS9 ATS HTTP/2 NSURLSession中说得很详细, 想了解的可以进去阅读.

NSURLSession 工作流程


那么如何使用NSURLSession像从前用NSURLConnection那样发送一个请求呢?

然后就可以在代理方法中处理各种事情了. 简单吧? 下面分task说明代理方法的调用情况..

身份验证或TLS握手

这是所有task都必须经历的一个过程. 当一个服务器请求身份验证或TLS握手期间需要提供证书的话, URLSession会调用他的代理方法URLSession:didReceiveChallenge:completionHandler:去处理., 另外, 如果连接途中收到服务器返回需要身份认证的response, 也会调用该代理方法.

重定位response

这也是所有task都有可能经历的一个过程, 如果response是HTTP重定位, session会调用代理的URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:方法. 这里需要调用completionHandler告诉session是否允许重定位, 或者重定位到另一个URL, 或者传nil表示重定位的响应body有效并返回. 如果代理没有实现该方法, 则允许重定位直到达到最大重定位次数.

DataTask

  1. 对于一个data task来说, session会调用代理的URLSession:dataTask:didReceiveResponse:completionHandler:方法, 决定是否将一个data dask转换成download task, 然后调用completion回调继续接收data或下载data.
    • 如果你的app选择转换成download task, session会调用代理的URLSession:dataTask:didBecomeDownloadTask:方法并把新的download task对象以方法参数的形式传给你. 之后代理不会再收到data task的回调而是转为收到download task的回调
  2. 在服务器传输数据给客户端期间, 代理会周期性地收到URLSession:dataTask:didReceiveData:回调
  3. session会调用URLSession:dataTask:willCacheResponse:completionHandler:询问你的app是否允许缓存. 如果代理不实现这个方法的话, 默认使用session绑定的Configuration的缓存策略.

DownloadTask

  1. 对于一个通过downloadTaskWithResumeData:创建的下载任务, session会调用代理的URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:方法.
  2. 在服务器传输数据给客户端期间, 调用URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:给用户传数据
    • 当用户暂停下载时, 调用cancelByProducingResumeData:给用户传已下好的数据.
    • 如果用户想要恢复下载, 把刚刚的resumeData以参数的形式传给downloadTaskWithResumeData:方法创建新的task继续下载.
  3. 如果download task成功完成了, 调用URLSession:downloadTask:didFinishDownloadingToURL:把临时文件的URL路径给你. 此时你应该在该代理方法返回以前读取他的数据或者把文件持久化.

UploadTask

上传数据去服务器期间, 代理会周期性收到URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:回调并获得上传进度的报告.

StreamTask

如果任务的数据是由一个stream发出的, session就会调用代理的URLSession:task:needNewBodyStream:方法去获取一个NSInputStream对象并提供一个新请求的body data.

task completion

任何task完成的时候, 都会调用URLSession:task:didCompleteWithError:方法, error有可能为nil(请求成功), 不为nil(请求失败)

  • 请求失败, 但是该任务是可恢复下载的, 那么error对象的userInfo字典里有一个NSURLSessionDownloadTaskResumeData对应的value, 你应该把这个值传给downloadTaskWithResumeData:方法重新恢复下载
  • 请求失败, 但是任务无法恢复下载, 那么应该重新创建一个下载任务并从头开始下载.
  • 因为其他原因(如服务器错误等等), 创建并恢复请求.

Note
NSURLSession不会收到服务器传来的错误, 代理只会收到客户端出现的错误, 例如无法解析主机名或无法连接上主机等等. 客户端错误定义在URL Loading System Error Codes. 服务端错误通过HTTP状态法进行传输, 详情请看NSHTTPURLResponse和NSURLResponse类.

销毁session

如果你不再需要一个session了, 一定要调用它的invalidateAndCancelfinishTasksAndInvalidate方法. (前者是取消所有未完成的任务然后使session失效, 后者是等待正在执行的任务完成之后再使session失效). 否则的话, 有可能造成内存泄漏. 另外, session失效后会调用URLSession:didBecomeInvalidWithError:方法, 之后session释放对代理的强引用.

Background Transport


需要注意的是, 在后台session中, 一些代理方法将失效. 下面说一些使用后台session的注意点 :

  • 后台session必须提供一个代理处理突发事件
  • 只支持HTTP(S)协议. 其他协议都不可用.
  • 只支持上传/下载任务, data任务不支持.
  • 后台任务有数量限制
  • 当任务数量到达系统指定的临界值的时候, 一些后台任务就会被取消. 也就是说, 一个需要长时间上传/下载的任务很可能会被系统取消然后有可能过一会再重新开始, 所以支持断点续传很重要.
  • 如果一个后台传输任务是在app在后台的时候开启的, 那么这个任务很可能会出于对性能的考虑随时被系统取消掉. . (相当于session的Configuration对象的discretionary属性为true.)

后台session限制确实很多, 所以尽可能使用前台session做事情.

Note

后台session最好用来传输一些支持断点续传大文件. 或对这个过程进行一些针对性的优化

  • 最好把文件先压缩成zip/tar等压缩文件再上传/下载.
  • 把大文件按数据段分别发送, 发送完之后服务端再把数据拼接起来.
  • 上传的时候服务端应该返回一个标识符, 这样可以追踪传输的状态, 及时做出传输的调整
  • 增加一个web代理服务器中间层, 以促进上述的优化

Usage

那么如何使用这个后台传输呢?

  • 创建一个后台session
  • 创建一个upload or download task
  • 我们等下载到一半后进入后台, 打开App Switcher过一会可以发现, 图片下载完之后就会显示在应用程序上. 方法调用顺序为 : 下面四个方法全部都是app在后台时调用的

总结后台传输

  1. 尽量用真机进行调试, 模拟器会跳过某一两个方法
  2. 只能进行upload/download task, 不能进行data task
  3. 不能使用带completionHandler的方法创建task, 否则程序直接挂掉
  4. Applecation里的completionHandler必须存储起来, 等你处理完所有事情之后再调用告诉系统可以进行Snapshot和挂起app了
  5. 后台下载最好支持断点续传, 因为任务有可能会被系统主动取消(例如系统性能下降了, 资源不够用的情况下)

后台传输的Demo在文章头部的地方, 也可以点这里进去

NSURLSession API


  • 创建Session

    • + sessionWithConfiguration: : 创建一个指定配置的session
    • + sessionWithConfiguration:delegate:delegateQueue: : 创建一个指定配置, 代理和代理方法执行队列的session
    • sharedSession : 返回session单例
  • 配置Session

    • configuration : 配置
    • delegate : 代理对象
    • delegateQueue : 代理方法的执行队列
    • sessionDescription : app定义的对于该session的描述
  • 添加data任务

    • - dataTaskWithURL: : 获取指定URL内容
    • - dataTaskWithURL:completionHandler: : 获取指定URL内容, 在completionHandler中处理数据. 该方法会绕过代理方法(除了身份认证挑战的代理方法)
    • - dataTaskWithRequest: : 获取指定URLRequest内容
    • - dataTaskWithRequest:completionHandler: : 获取指定URLRequest内容, 在completionHandler中处理数据. 该方法会绕过代理方法(除了身份认证挑战的代理方法)
  • 添加download任务

    • - downloadTaskWithURL: : 下载指定URL内容
    • - downloadTaskWithURL:completionHandler: : 下载指定URL内容, 在completionHandler中处理数据. 该方法会绕过代理方法(除了身份认证挑战的代理方法)
    • - downloadTaskWithRequest: : 下载指定URLRequest内容
    • - downloadTaskWithRequest:completionHandler: : 下载指定URLRequest内容, 在completionHandler中处理数据. 该方法会绕过代理方法(除了身份认证挑战的代理方法)
    • - downloadTaskWithResumeData: : 创建一个之前被取消/下载失败的download task
    • - downloadTaskWithResumeData:completionHandler: : 创建一个之前被取消/下载失败的download task, 在completionHandler中处理数据. 该方法会绕过代理方法(除了身份认证挑战的代理方法)
  • 添加upload任务

    • - uploadTaskWithRequest:fromData: : 通过HTTP请求发送data给指定URL
    • - uploadTaskWithRequest:fromData:completionHandler: : 通过HTTP请求发送data给指定URL, 在completionHandler中处理数据. 该方法会绕过代理方法(除了身份认证挑战的代理方法)
    • - uploadTaskWithRequest:fromFile: : 通过HTTP请求发送指定文件给指定URL
    • - uploadTaskWithRequest:fromFile:completionHandler: : 通过HTTP请求发送指定文件给指定URL, 在completionHandler中处理数据. 该方法会绕过代理方法(除了身份认证挑战的代理方法)
    • uploadTaskWithStreamedRequest : 通过HTTP请求发送指定URLRequest数据流给指定URL
  • 添加stream任务

    • - streamTaskWithHostName:port: : 通过给定的域名和端口建立双向TCP/IP连接
    • - streamTaskWithNetService: : 通过给定的network service建立双向TCP/IP连接
  • 管理session

    • finishTasksAndInvalidate : 任务全部完成后销毁session
    • flushWithCompletionHandler: : 清除硬盘上的cookies和证书, 清理暂时的缓存, 确保未来能响应一个新的TCP请求
    • getTasksWithCompletionHandler: : 异步调用session中所有upload, download, data tasks的completion回调.
    • invalidateAndCancel : 取消所有未完成的任务并销毁session
    • resetWithCompletionHandler: : 清空cookies, 缓存和证书存储, 移除所有磁盘文件, 清理正在执行的下载任务, 确保未来能响应一个新的socket请求

API总结

所有创建task的方法, 只要带有completionHandler这个参数的, 均表示为请求过程中不会触发代理方法. 所有不带有completionHandler这个参数的, 均会走代理方法流程.

如果你实现了URLSession:didReceiveChallenge:completionHandler:方法又没有在该方法调用completionHandler, 请求就会遭到阻塞

断点续传

  • 下载失败/暂停/被取消, 可以通过task的- cancelByProducingResumeData:方法保存已下载的数据, 然后调用session的downloadTaskWithResumeData:方法, 触发代理的URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:方法

Something else Important


NSCopying Behavior

session, task和configuration对象都支持copy操作 :

  • session/task copy : 返回session对象本身
  • configuration copy : 返回一个无法修改(immutable)的对象.
线程安全

URLSession 的API全部都是线程安全的. 你可以在任何线程上创建session和tasks, task会自动调度到合适的代理队列中运行.

Warning

后台传输的代理方法URLSessionDidFinishEventsForBackgroundURLSession:可能会在其他线程中被调用. 在该方法中你应该回到主线程然后调用completion handler去触发AppDelegate中的application:handleEventsForBackgroundURLSession:completionHandler:方法.

常量
  • NSURLSession-Specific NSError userInfo Dictionary Keys : NSURLSession API 中出现的NSError的userInfo的keys
  • Background Task Cancellation reasons : 指示系统为什么取消了你的后台任务的理由
  • Transfer Size Constant : 指示一个未知传输大小的常量

参考文档

NSURLSession – Foundation
WWDC 2013 – Session 204 – What’s New with Multitasking
WWDC 2013 – Session 705 – What’s New in Foundation Networking
WWDC 2015 – Session 711 – Networking with NSURLSession
WWDC 2016 – Session 711 – NSURLSession: New Features and Best Practices

Github : Jerry4me, Demo : JRBgSessionDemo

打赏支持我写出更多好文章,谢谢!

打赏作者

打赏支持我写出更多好文章,谢谢!

任选一种支付方式

1 3 收藏 评论

关于作者:Jerry4me

广工大-大三生 iOS Dev 个人主页 · 我的文章 · 9 ·     

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部