此文章为引用,原文链接:https://www.jianshu.com/p/2501a618ad1b
一、首先聊一聊这些年苹果爸爸对网络层的改变
1、在2003年随着第一版Safari的发布就发布了NSURLConnection。 作为网络基础架构,这些年也服务了成千上万的iOS和Mac OS程序,也做得相当的不错。
2、2013年苹果推出iOS 7,这个版本的iOS被称为革命性的iOS,而我们以前所用到的NSURLConnection这个网络库也被苹果爸爸给重构, 并且重新命名为NSURLSession。
3、2014年的iOS 8苹果爸爸宣布正式淘汰NSURLConnection,同时发布了App Extensions。
4、2015年iOS 9苹果爸爸让网络变得更加安全了。
(1)App Transport Security (ATS):核心目的是防止意外泄露用户的敏感数据,ATS加强了NSURLSession的默认策略,现在的NSURLSession不允许明文的HTTP加载,他只会使用HTTPS连接。
(2)NSURLSession支持HTTP/2协议:如果已经在程序中使用NSURLSession,所有的这些都会自动转换。HTTP/2协议已经无缝集成到NSURLSession的API中,不需要修改任何代码工程就可支持HTTP/2协议。
(3)watchOS 支持NSURLSession:之前iWatch通过蓝牙和iphone连接,iphone下载完成在通过蓝牙传到watch。现在watch可以通过使用NSURLSession自己下载。
(4)共享cookies:iOS8推出了App Extension,之前App与其Extension都默认对数据的持有进行独立处理,就像放在两个不同的数据集合里,而现在可以让App和其Extension共享同一个数据集合。具体代码如下:
(5)增加NSURLSessionStreamTask:以前我们使用NSInputStream/NSOutputStream来进行一些非HTTP的连接, 例如利用TCP连接一台远程的服务器等等, 现在我们有了NSURLSessionStreamTask让我们更简单地实现以上功能。
6、2016年 iOS 10新增NSURLSessionTaskMetrics和NSURLSessionTaskTransactionMetrics:对发送请求/DNS查询/TLS握手/请求响应等各种环节时间上的统计。更易于我们检测, 分析我们的请求缓慢到底是发生在哪个环节,并对此进行优化提升我们APP的性能。
7、2017年iOS 11苹果爸爸在网络层做了新增和优化了。
(1)提供了NSProgressReporting协议,且NSURLSessionTask实现了这个协议,让我们能够获得progress对象,这个progress对象可以以0~1.0的方式告诉你当前进度,而不用你自己去拿到已获得的数据量去除以需要获得的数据总量从而得出进度。然后这个progress对象跟NSURLSessionTask的绑定是双向的:你调用progress对象的cancel、pause、resume也会使得task变为cancel、pause、resume,反之亦然。
(2)新增waitsForConnectivity属性,以前进行网络调用时,如果网络不通,那么系统就会报个错告诉你网络不通。这时候你要么轮询要么让用户手动retry,然后网络通了请求才能发送出去。现在只需要把NSURLSessionConfiguration的waitsForConnectivity设置成YES,这样如果请求发送的时候网络不通,那么这个请求就会等到网络通了的时候再发出去。
(3)在NSURLSessionTask的delegate里面新增了一个方法urlSession:task:willBeginDelayedRequest:completionHandler:系统在发起请求之前会调一个这个回调,然后在这个completionHandler里面你告诉系统这个请求是否要发出去,是否要修改。(因为创建的后台请求在还没发出去的时候可能因为上下文变化的原因导致这个请求无意义)
(4)earliestBeginDate属性,在后台请求的时候以前系统不知道什么时候去发起你的请求才是最合适的 ,现在给task设置一个earliestBeginDate,系统在这之前是不会发起请求的。
(5)iOS 11可以通过设置NSURLSessionTask的countOfBytesClientExpectsToSend和countOfBytesClientExpectsToReceive来让系统更好地调度你的后台网络任务。
(6)multipathServiceType:移动设备多路协议,这使得移动设备的TCP包可以在这两个(多个)链路上随意切换着发(同时开启两个流量链路),而不必断线重连。
(7)iOS 11支持ECN(显式拥塞通知)可以最大化的使用网络带宽,减少包的重发次数,降低延迟。还有iOS 11里的网络操作被移动到User Space去了。iOS11支持Brotli压缩算法。iOS11更新了Public Suffix List。
8、未来会有哪些变化
(1)TLS1.3:苹果要把网络库整体迁移到支持TLS1.3,年底TLS1.3的标准应该能出来。现在基于TLS1.3草稿的实现可以弄下来自己测试着玩了。最新的TLS1.3草稿已经出到21了:draft-ietf-tls-tls13-21。
(2)QUIC:Google搞了个QUIC,苹果在跟进。QUIC可以理解成UDP实现的TCP+TLS+HTTP/2集合体。主要是提高了数据传输效率和链接效率。目前QUIC的开发才刚刚开始,项目网站提供了玩具客户端和玩具服务端给大家玩:Playing with QUIC。
二、聊一聊NSURLSession
1、支持data, ftp, http(s)协议, 同时支持代理服务器和socks网关.
2、支持http/1.1, http/2, spdy协议, 但同时需要服务器支持ALPN和NPN.
3、ALPN(Application Layer Protocol Negotiation,应用层协议协商)
4、NPN(Next Protocol Negotiation,下一代协议协商)
5、NPN是服务端发送它支持的HTTP协议列表, 供客户端选择; 而ALPN则相反,由客户端发送它支持的HTTP协议列表, 供服务端选择。如果缺少NPN/ALPN其中一个, 则无法使用HTTP/2通信。
想详细了解HTTP/2的可以参考这两篇文章为什么我们应该尽快支持 ALPN和谈谈 HTTP/2 的协议协商机制
6、NSURLSession相关类为 :
• NSURLSession
• NSURLSessionConfiguration
• NSURLSessionDelegate
• NSURLSessionTask
• NSURLSessionTaskMetrics
• NSURLSessionTaskTransactionMetrics
用一个简单的图来表示一下他们各个类之间的关系:
(一)同名类NSURLSession
初始化方式:
1、 [NSURLSession sharedSession];全局单例session,有一定的局限性。
2、自定义配置文件,设置代理
[NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
[NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
3、后台session(由NSURLSessionConfiguration)配置。
(二)NSURLSessionConfiguration
NSURLSessionConfiguration 有三个类方法,这很好地说明了 NSURLSession 设计时所考虑的不同的使用场景。
1、+defaultSessionConfiguration 返回一个标准的 configuration,共享 NSHTTPCookieStorage,共享 NSURLCache 和共享 NSURLCredentialStorage。
2、+ephemeralSessionConfiguration 返回一个预设配置,这个配置中不会对缓存,Cookie 和证书进行持久性的存储。这对于实现像秘密浏览这种功能来说是很理想的。
3、+backgroundSessionConfiguration:(NSString *)identifier 创建一个后台 session。后台 session 不同于常规的,普通的 session,它甚至可以在应用程序挂起,退出或者崩溃的情况下运行上传和下载任务。
NSURLSessionConfiguration拥有20多个配置属性:
1、基本配置
(1)HTTPAdditionalHeaders 指定了一组默认的可以设置出站请求的数据头。这对于跨 session 共享信息,如内容类型,语言,用户代理和身份认证,是很有用的。
(2)networkServiceType 对标准的网络流量,网络电话,语音,视频,以及由一个后台进程使用的流量进行了区分。
(3)allowsCellularAccess 和 discretionary 被用于节省通过蜂窝网络连接的带宽。对于后台传输的情况,推荐大家使用 discretionary 这个属性,因为allowsCellularAccess会把 WiFi 和电源的可用性考虑在内。
(4)timeoutIntervalForRequest 和 timeoutIntervalForResource 分别指定了对于请求和资源的超时间隔。
(5)HTTPMaximumConnectionsPerHost可以在需要时限制连接到特定主机的数量。
(6)sessionSendsLaunchEvents 指定该 session 是否应该从后台启动。
(7)connectionProxyDictionary 指定了 session 连接中的代理服务器。
(8)waitsForConnectivity如果请求发送的时候网络不通,那么这个请求就会等到网络通了的时候再发出去。
(9)multipathServiceType多路多协议网络操作这使得移动设备的TCP包可以在这两个(多个)链路上随意切换着发(同时开启两个流量链路),而不必断线重连。效果就是:Wi-Fi和Cellular可以共存,相互辅助。
(10)NSURLSessionMultipathServiceTypeHandover(可靠模式)
(11)这种模式下优先考虑的是链接的可靠性。只有在Wi-Fi信号不好的时候,流量才会走Cellular。如果Wi-Fi信号好,但是Wi-Fi很慢,这时候也不会切到Cellular链路。
(12)NSURLSessionMultipathServiceTypeInteractive(低延时模式)
(13)这种模式下优先考虑的是链接的低延时。系统会看Wi-Fi快还是Cellular快。如果Cellular比Wi-Fi快,哪怕此时Wi-Fi信号很好,系统也会把流量切到Cellular链路。
(14)NSURLSessionMultipathServiceTypeAggregate(混合模式)
(15)在这种模式下,Wi-Fi和Cellular会同时起作用。如果Wi-Fi是1G带宽,Cellular也是1G带宽,那么你的设备就能享受2G带宽。(不能用于生产环境)
(16)需要注意的是,Multipath Protocols for Mobile Devices这个功能同时也需要服务端支持MPTCP(Multipath TCP)才行,如果服务端不支持的话,光客户端支持没用。linux起了一个项目在做这个事情,项目地址:https://multipath-tcp.org。有兴趣的同学可以自己去看一下。
2、Cookie策略
HTTPCookieStorage存储了session所使用的cookie。两种初始化方式
(1)[NSHTTPCookieStorage sharedHTTPCookieStorage];
(2)[NSHTTPCookieStorage sharedCookieStorageForGroupContainerIdentifier:@"identifier"];iOS 9新增。
(3)NSHTTPCookieAcceptPolicy决定了什么情况下 session 应该接受从服务器发出的 cookie。
3、安全策略
(1)URLCredentialStorage存储了NSURLSession所使用的证书,默认使用+sharedCredentialStorage单例对象。
(2)TLSMinimumSupportedProtocol和TLSMaximumSupportedProtocol确定session是否支持SSL协议。
4、缓存策略
(1)URLCache是 session 使用的缓存。默认情况下会使用 NSURLCache 的 +sharedURLCache 这个单例对象。
(2)requestCachePolicy 指定了一个请求的缓存响应应该在什么时候返回。
5、自定义协议
(1)protocolClasses 用来配置特定某个 session 所使用的自定义协议(该协议是 NSURLProtocol 的子类)的数组。
(三)NSURLSessionDelegate
session管理的一组tasks共享一个代理, 不想实现代理方法时, 代理传nil即可。代理协议分为 :
(1)NSURLSessionDelegate : session-level的代理方法。
(2)NSURLSessionTaskDelegate : task-level面向all的代理方法。
(3)NSURLSessionDataDelegate : task-level面向data和upload的代理方法。
(4)NSURLSessionDownloadDelegate : task-level的面向download的代理方法。
(5)NSURLSessionStreamDelegate : task-level的面向stream的代理方法。
(四)NSURLSessionTask
当一个 NSURLSessionDataTask 完成时,它会带有相关联的数据,一般来说,服务端对于一个上传任务的响应也会有相关数据返回,所以 NSURLSessionUploadTask 继承自 NSURLSessionDataTask。
task 是由一个 NSURLSession 创建的。每个 task 的构造方法都对应有或者没有 completionHandler 这个 block 的两个版本,例如:有这样两个构造方法 –dataTaskWithRequest: 和 –dataTaskWithRequest:completionHandler:。
(1)NSURLSessionTask : Task的抽象基类。
(2)NSURLSessionDataTask : 以NSData的形式接收一个URLRequest的内容。
(3)NSURLSessionUploadTask : 上传NSData或者本地磁盘中的文件, 完成后以NSData的形式接收一个URLRequest的响应。
(4)NSURLSessionDownloadTask : 下载完成后返回临时文件在本地磁盘的URL路径。
(5)NSURLSessionStreamTask : 用于建立一个TCP/IP连接。
以前我们使用NSInputStream/NSOutputStream来进行一些非HTTP的连接, 例如利用TCP连接一台远程的服务器等等, 现在我们有了NSURLSessionStreamTask让我们更简单地实现以上功能。
NSURLSessionStreamTask的特性 :
(1)更轻松地使用TCP进行通信。
(2)替代NSInputStream/NSOutputStream, 提供更优的API。
(3)异步读写API。
(4)能自动通过HTTP代理, 连接一个远程服务器。
(5)轻松转换成NSStream。
(五)NSURLSessionTaskMetrics 和 NSURLSessionTaskTransactionMetrics
对发送请求/DNS查询/TLS握手/请求响应等各种环节时间上的统计。更易于我们检测,分析我们App的请求缓慢到底是发生在哪个环节,并对此进行优化提升我们APP的性能。
NSURLSessionTaskMetrics对象与NSURLSessionTask对象一一对应。每个NSURLSessionTaskMetrics对象内有3个属性 :
(1)taskInterval : task从开始到结束总共用的时间
(2)redirectCount : task重定向的次数
(3)transactionMetrics : 一个task从发出请求到收到数据过程中派生出的每个子请求, 它是一个装着许多NSURLSessionTaskTransactionMetrics对象的数组。每个对象都代表下图的一个子
API很简单,就一个方法 : - (void)URLSession: task: didFinishCollectingMetrics:, 当收集完成的时候就会调用该方法。
fetchStart : 开始发起请求.
domainLookupStart : 发送DNS请求, 域名->IP地址
domainLookupEnd : DNS请求完成, 拿到IP地址
connectStart : 与远程服务器开始建立TCP连接
secureConnectionStart : HTTPS的TLS握手开始
secureConnectionEnd : HTTPS的TLS握手完成connectEnd : 与服务器建立起了TCP连接
requestStart : 开始传输HTTP header第一个字节的时间(远程/缓存)
requestEnd : HTTP最后一个字节传输完成的时间(远程/缓存)
responseStart : 从服务器得到数据(远程/缓存)
responseEnd : 从服务器接受完最后一个字节的数据(远程/缓存)
(六)NSSession的整个工作流程
(1)身份验证或TLS握手:
这是所有task都必须经历的一个过程. 当一个服务器请求身份验证或TLS握手期间需要提供证书的话会调用这个代理。另外, 如果连接途中收到服务器返回需要身份认证的response, 也会调用这个代理方法。
(2)重定位response:
如果response是HTTP重定位, session会调用下面的代理,这里需要调用completionHandler告诉session是否允许重定位,或者重定位到另一个URL,或者传nil表示重定位的响应body有效并返回。如果代理没有实现该方法,则允许重定位直到达到最大重定位次数。
(3)DataTask
<1> 对于一个data task来说,session会调用代理决定是否将一个dataTask转换成download task,然后调用completion回调继续接收data或下载data。
<2> 在服务器传输数据给客户端期间, 代理会周期性地收到
<3> session会调用下面代理询问你的app是否允许缓存。 如果代理不实现这个方法的话,默认使用session绑定的Configuration的缓存策略。
(4)DownloadTask
<1>session先会调用代理方法。
<2>在服务器传输数据给客户端期间, 调用下面代理给用户传数据。
<3>当用户暂停下载时,调用cancelByProducingResumeData:给用户传已下好的数据。
<4>如果用户想要恢复下载,把刚刚的resumeData以参数的形式传给downloadTaskWithResumeData:方法创建新的task继续下载。
<5>如果download task成功完成了,调用下面代理把临时文件的URL路径给你。此时你应该在该代理方法返回以前读取他的数据或者把文件持久化。
(5)UploadTask
上传数据去服务器期间, 代理会周期性收到URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:回调并获得上传进度的报告。
(6)StreamTask
如果任务的数据是由一个stream发出的, session就会调用代理的URLSession:task:needNewBodyStream:方法去获取一个NSInputStream对象并提供一个新请求的body data。
(7)task completion
任何task完成的时候, 都会调用URLSession:task:didCompleteWithError:方法,error有可能为nil(请求成功),不为nil(请求失败)
(8)销毁session
如果你不再需要一个session了, 一定要调用它的invalidateAndCancel或finishTasksAndInvalidate方法来释放session否则会造成内存泄露。
(七)苹果爸爸建议的最佳网络实践
1、IPv6
苹果爸爸说:IPv6各种好,大家快来用。如果你不支持IPv6,爸爸就不让你上架。要支持IPv6的话,老老实实用NSURLSession或者CFNetwork就OK了。
不要做的事情:
(1)不用历史遗留的IPv4 API。
(2)不要直接用IPv4的地址做链接,应该用域名去做请求。
(3)发包前不要做各种检查:比如你在建立链接之前想看一下我当前这个设备是不是IPv4的地址,这种做法就不行。
(4)不要直接使用socket去发起请求。
2、不要引入其他的网络库,要使用苹果自己的API
苹果并不是在说AFNetworking、Alamofire不能用。这些第三方库本质上还是基于NSURLSession,也就是苹果的API去开发的。所以用它们没问题。苹果的意思是不希望你使用别的基于Socket开发的网络库,例如:ACE、Asio这些。
3、一般来说一个App就一个NSURLSession就够了
以前迁移NSURLConnection到NSURLSession的时候,有人每次都创建新的NSURLSession,但事实上这是没必要的。各个并行的NSURLSessionTask可以共享同一个NSURLSession。如果你使用了多个NSURLSession的话,记得清理就好,不清理是会产生内存泄漏的。
4、NSURLSession的delegate方法和block方法不要同时使用
如果你用了block,那么delegate就不会回调了。这事情仅有两个特例是两个都回调的:taskIsWaitingForConnectivity和didReceiveAuthenticateChallenge。
三、了解一下Network Extension Framework
iOS 9 发布之后,推出NetworkExtension, 它可给系统WiFi列表列表里边的WiFi设置密码 、标签(副标题) 来直接点击连接。 还可获取整个WiFi列表,建立VPN等。
1、iOS 11新增了两个类:NEHotSpotConfiguration,NEDNSProxyProvider。
(1)NEHotSpotConfiguration
NEHotSpotConfiguration可以让你的智能设备在链接手机App之后,能够很方便地通过在手机App上的操作来实现热点的链接。例如你买了一个网络摄像头,你想要连上摄像头的Wi-Fi热点去配置这个摄像头的话,以前要这么操作:
现在用NEHotSpotConfiguration就能很方便地搞定事情了:
当然,这套API也可以被拿来模拟各种网络环境,在测试App的时候很有用。
(2)NEDNSProxyProvider
NEDNSProxyProvider可以用来设置你的手机如何跟DNS做交互。你可以自己发DNS请求,也可以自己基于不同的协议去做DNS查询。例如DNS over TLS,DNS over HTTP。