iOS版本导致NSURLSession表现不一致问题调研

问题描述

iOS8.1系统上,使用Cronet测试网络请求,请求未能使用经过Cronet发送,仍使用iOS自身系统网络库。在iOS 9及以上系统未出现这个问题。

在iOS上,Cronet是这样代理网络请求的。
我们注册了自定义的NSURLProtocol类来拦截NSURLSession请求,注册流程可以分为这些步骤:

  1. sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]初始化一个默认的NSURLSessionConfiguration。

  2. [Cronet registerNSURLSessionConfiguration:sessionConfig]将CroNet自定义的NSURLProtocol class加入到sessionConfig中。
    使用sessionConfig初始化一个NSURLSession,之后发请求使用NSURLSession即可。

  3. CroNet还提供unregisterNSURLSessionConfiguration:(NSURLSessionConfiguration*)方法,可以将参数SessionConfig中的自定义protocol抹除,恢复成系统默认SessionConfig。我们期待的效果是,只要调用了该方法,网络请求就开始走系统默认路径而不是CroNet。

在我们的测试demo中,使用了一个全局的NSURLSession,启动时会start CroNet,并将上述1、2步骤都执行好,此时执行普通的发送请求,会命中CroNet,结果符合预期。

然后我执行了步骤3,unregister之后,网络请求又会回到系统网络库,此时依然符合预期。

那么如果我如果再执行1、2,请求居然不会再命中CroNet了,此处不符合预期了。

初步调研原因

经过打印内存地址,最终确定原因为:

步骤1产生了一个新的defaultSessionConfiguration,这样导致sessionConfig已经不是步骤3中NSURLSession中引用的sessionConfig,使用新的sessionConfig进行步骤3并不会对NSURLSession中原本的sessionConfig产生影响,因此导致之后的请求依然走系统库,进不去CroNet。

问题疑点

问题其实还有个重要的问题没解释:
为啥问题在iOS9/10没有出现

我们做了对照实验,唯一变量是系统版本,iOS8必现问题,iOS9/10没有问题,那必然是不同版本之间在一些系统级别的实现存在diff。这种diff会带来新的问题吗

在参考了苹果官方文档后,确定了根本原因:
iOS_consist
NSURLSession - Configuration by Apple documentation
参看苹果开发者文档的解释,从iOS9开始,苹果是把NSURLSessionConfiguration拷贝了一份放在NSURLSession中,而在iOS9以下只是一个引用。

因此文档中也这么提醒了,在iOS9及以上,对于源sessionConfig的任何修改都不会影响到NSURLSession上,因此CroNet stop(会将源sessionConfig中的自定义NSURLProtocol remove掉)之后,NSURLSession不受影响,它依然认为自己应该将请求灌到CroNet中去,所以未察觉出问题。

解决方案

解决方案

  1. 高版本(iOS 9/10 及以上)的NSURLSession一经创建,就无法修改其SessionConfig,因此影响1目前无有效方法解决。只能声明在高版本中,如果要更换NSURLSession中的SessionConfig,需重新创建NSURLSession。
  1. 修改代码, 在CroNet stop之后,使canInitWithRequest方法妥善处理灌入的请求:即CroNet running的时候会走CroNet,被stop后,返回false并走系统网络库。

  2. 在CroNet stop后把unregisterAllNSURLSessionConfiguration步骤去掉,这样在stop后各个版本还是均尝试是否走CroNet,由于在上面2中对canInitWithRequest进行了修复,因此不会对业务造成影响。