NSURLSession介绍
NSURLSession是苹果对网络会话的封装,可以完全替代原来的NSURLConnection。相比于NSURLConnection,NSURLSession具备以下优势:
与NSURLConnection相比不再需要处理RunLoop相关的东西
支持HTTP/2 和 HTTP/3协议
支持在APP未运行或挂起时进行后台下载/上传
提供了全局的session,使用方便
提供了丰富的代理方法
支持监测整个HTTP事务各个阶段的网络指标
支持的协议
1 | The URLSession class natively supports the data, file, ftp, http, and https URL schemes, with transparent support for proxy servers and SOCKS gateways, as configured in the user’s system preferences. |
URLSession原生支持data, file, ftp, http, 和 https协议,当然你也可以子类化URLProtocol来支持自己的私有网络协议。URLSession支持的HTTP版本为HTTP/1.1, HTTP/2, 和 HTTP/3,当然要想使用到HTTP/2, HTTP/3服务器端也必须得支持才行。
URLSessionConfiguration
httpMaximumConnectionsPerHost
同一时间,同一个host的最大http连接数量,默认6个。该限制是针对单个session的,如果你有多个session,那么你的App可能就会超过这个限制。受当前连接资源影响,单个session的实际httpMaximumConnectionsPerHost可能会小于你设置的值。
NSURLSession的基础用法
1 | - (void)viewDidLoad { |
对于方法
1 | + (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration |
delegate和delegateQueue会被session强引用。只有当session被 invalidate后,delegate在URLSession:didBecomeInvalidWithError
结束后才会被释放.对于delegateQueue,实际使用时delegateQueue最好不要设置为主队列(会增加主线程负担).当delegateQueue不是主队列时,didReceiveData:方法将随机在某个线程执行.
基本上一个APP,生成一个urlSession就够了.没必要一次请求,创建一个session,请求结束后又将session Invalidate.因此也就没必要去管delegate和delegateQueue的内存释放问题,这三个对象基本上是等到APP结束才会销毁的.一般的做法是常规的API请求使用一个session,上传下载的请求使用另一个session。
对于代理方法:
1 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask |
在该方法中,为什么收到响应后,还要调用completionHandler?
因为在该方法中,通过disposition参数,调用completionHandler后,可以更细粒度的控制本次请求是继续还是取消还是转为下载任务.如果是取消,则后面请求的响应体不会接收.如果是转为下载任务,那么通过调用completionHandler,NSURLSession将调用Delegate的 URLSession:dataTask:didBecomeDownloadTask:
方法并将新生成的Download task对象作为参数传入。在此调用之后,Delegate将不再接收来自Data task的回调消息,并开始接收Download task的回调消息。
注意:如果不调用
1 | NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow; |
后面的didReceiveData:代理方法将不会执行.
delegateQueue
回调的派发队列,方法参数说明:
1 | An operation queue for scheduling the delegate calls and completion handlers. The queue should be a serial queue, in order to ensure the correct ordering of callbacks. If nil, the session creates a serial operation queue for performing all delegate method calls and completion handler calls. |
用于执行delegate回调和完成处理的派发队列,delegateQueue最好是一个串行队列,这样可以确保正确的回调顺序。默认是一个串行队列。注意这个只是回调方法的派发队列,和网络请求的线程不是同一个。
1 | 网络请求线程 回调队列 |
这里文档是should,如果用个并发队列会怎样?我们项目里用的就是并发队列,用了这么久好像也没什么影响。
场景:点击按钮,发起1个请求,故意阻塞didReceiveData回调。
1 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data |
即使delegateQueue的最大并发数设置为3,但是didReceiveData的回调行为还是类似串行(虽然每次线程确实会不一样),不会出现两个线程同时回调didReceiveData。
通过实验可以得出:
1.delegateQueue的最大并发数设置为1,那么回调就是串行的,由于多个请求共用这一个回调派发队列,如果其中一个请求的代理方法比较耗时,那么其他请求的回调将会等待前面的回调。
1 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data |
不过一般情况下didReceiveData不可能出现这种故意阻塞的写法,所以delegateQueue的并发数设置为1几乎没有什么影响,如果没有什么特别的理由还是按文档上来设置为1吧。
2.delegateQueue的最大并发数设置为3(大于1的情况),那么回调就是并发的,目前没看到有什么异常影响。因为是并发队列,其他请求的回调不用等待前面的回调完成因而可以快速被回调。
3.即使delegateQueue的最大并发数设置为3,对于同一个请求的回调,didReceiveData的回调行为还是类似串行(虽然每次线程确实会不一样),不会出现两个线程同时回调didReceiveData。
defaultSession 和 ephemeralSession
ephemeralSession,不会将响应缓存到磁盘,也不会使用磁盘缓存的响应。最多将一些session-related的数据保存在内存。APP结束,所有会话信息都会随着内存回收而被清除。
defaultSession,默认session,如果响应能够缓存则会将响应持久化到磁盘。
问答
1.NSURLSession对象是被谁强引用了?如何释放?
NSURLSession对象应该是被系统的runloop强引用了,就类似于定时器一样,需要invalid后,才会被释放销毁.
题外话:如果timer属性是strong,那么invalidate后最好将其置为nil,否则invalid后timer因为还有人持有它,而不能销毁.strong情况下,timer的释放: [self.timer invalidate];self.timer = nil;
定时器对象是注册到runloop里的,应该通过invalidate来告诉runloop释放它.所以self不应该持有该对象,因此timer属性最好为weak.
2. 在didCompleteWithError:完成的时候,之前收到的data怎么取到?不借助其他的变量,在该方法里取不到?
完成回调里是取不到data的。需要在相应的代理方法里保存数据。
3. 请求转为下载任务时,为什么会转移到其他的代理方法比如didBecomeDownloadTask?
这是因为下载任务的资源一般较大,接收到的数据不能像普通请求那样直接缓存在内存,否则会导致APP OOM。所以必须转移到其他代理方法边接收边缓存到磁盘。
4.TCP接收到数据后是怎么传给NSURLSession的,NSURLSession又是怎么传给delegateQueue执行各个代理方法的,线程关系?TODO
不知道。