问题描述
iOS8.1系统上,使用Cronet测试网络请求,请求未能使用经过Cronet发送,仍使用iOS自身系统网络库。在iOS 9及以上系统未出现这个问题。
在iOS上,Cronet是这样代理网络请求的。
我们注册了自定义的NSURLProtocol类来拦截NSURLSession请求,注册流程可以分为这些步骤:
sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]初始化一个默认的NSURLSessionConfiguration。
[Cronet registerNSURLSessionConfiguration:sessionConfig]将CroNet自定义的NSURLProtocol class加入到sessionConfig中。
使用sessionConfig初始化一个NSURLSession,之后发请求使用NSURLSession即可。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会带来新的问题吗
在参考了苹果官方文档后,确定了根本原因:
NSURLSession - Configuration by Apple documentation
参看苹果开发者文档的解释,从iOS9开始,苹果是把NSURLSessionConfiguration拷贝了一份放在NSURLSession中,而在iOS9以下只是一个引用。
因此文档中也这么提醒了,在iOS9及以上,对于源sessionConfig的任何修改都不会影响到NSURLSession上,因此CroNet stop(会将源sessionConfig中的自定义NSURLProtocol remove掉)之后,NSURLSession不受影响,它依然认为自己应该将请求灌到CroNet中去,所以未察觉出问题。
解决方案
解决方案
- 高版本(iOS 9/10 及以上)的NSURLSession一经创建,就无法修改其SessionConfig,因此影响1目前无有效方法解决。只能声明在高版本中,如果要更换NSURLSession中的SessionConfig,需重新创建NSURLSession。
修改代码, 在CroNet stop之后,使canInitWithRequest方法妥善处理灌入的请求:即CroNet running的时候会走CroNet,被stop后,返回false并走系统网络库。
在CroNet stop后把unregisterAllNSURLSessionConfiguration步骤去掉,这样在stop后各个版本还是均尝试是否走CroNet,由于在上面2中对canInitWithRequest进行了修复,因此不会对业务造成影响。