谨以此文纪念多个日夜不停定位问题填坑的日子。
近期,有个bug映入眼帘,主要是某段程序对http的数据进行截取,用于进行二次数据分析,然后截取数据的方式主要对系统自带的方式发送请求可以截获,也可以回调,用第三方的库则不能回调,不知道哪里环节出来问题。
首先,苹果在 iOS9 之后已经放弃了 NSURLSession所以在现在的实际开发中,一般使用的是 iOS7 之后推出的NSURLSession。
https://www.jianshu.com/p/a8fc22afb739对NSURLSession进行了比较全面的讲解,另外,AFNetWorking的源码解析也比较多,比如:
【原】AFNetworking源码阅读(一) - 桑果 - 博客园 这个非常推荐,博主写了大量的中文注释,基本上把主要的方法都逐个讲解,良心之作
https://www.jianshu.com/p/b98cf91b9ce2
基本上是在session的基础上进行了封装,包括各种代理的回调、跟UI的互动、安全性、序列化等,这里主要关注对代理的回调,
第一反应,是http截取的代码与第三方库的代码存在冲突
于是将截取数据模块代码和第三方库(AFNetWork)的代码进行了全盘浏览,发现,两者代码比较相近🧐,事情貌似不简单,初步怀疑是部分代码重复,或者多次hook导致的问题,于是对两者的单例、线程安全、死锁、Hook等地方进行了重点探寻,结果,日子一天一天过,两天后一无所获。。。。。。。
事实上AFNetWork的稳定安全一直比较不错,甚至很多方法都加了sately前缀,比如
单例、锁、安全前缀一应俱全
好吧,还是老老实实从复现场景-->锁定代码-->找到问题的顺序一步一步走
为了好定位,直接将AFNetWork的源码倒入,debug走起
先用官方request实现一波
一个简单的NSURLSession的request请求,上报情况能够通过回调方法进行展示
在用AFNetwork的方式实现一波
这一次,回调没有反应。现象比较好复现
下一步,定位代码
AFNetwork的代码层数比较多,但核心的方法不多,网上有很多的源码讲解的文章,大家可以参考下
https://www.jianshu.com/p/a360140bf220 ,还有很多分章节分类名进行解析,这里就不一一分享了
,通过get方法回溯,找到AFNetwork对应使用NSURLSession建立连接的地方
这里有个知识点AFURLSessionManager是AFHTTPSessionManager父类,NSURLSession的申请在AFURLSessionManager进行了实现,如下
可以看到AFNetwork也使用了session,只是实现的方式不一样,除了加入默认配置文件,还实现了delegate的方式
调用时datataskwithRequest进行了添加。
那如果直接使用这种方式可以回调吗?
如图:
居然回调不起作用了!
至此,AFNetwork表示,这锅我不背🧐
那目标明确了,就是http截取的业务代码了
一般来说,在控制变量法的原则下,按照先变配置模块、再变初始化模块、接着变主路径、最后变分模块定位的顺序依次排查。
从启动模块开始排查,发现首先进入配置初始化,
打开看下配置内容
看起来就第一个比较可疑
关掉试下
居然成功回调了!
找下allowInterveneNetwork涉及的地方
主要有三个方法
xxx_urlSessionTaskDidStart,xxx_urlSessionTaskDidStop,xxx_sessionWithConfiguration
通过命名就可以看出来,第一个是task任务开始前,第二个任务是task结束后,第三个任务是进行初始配置
三个方法分别关闭,逐个回调情况
xxx_urlSessionTaskDidStart和xxx_urlSessionTaskDidStop分别关闭时偶尔出现回调,一起关闭时偶尔出现回调
xxx_sessionWithConfiguration关闭时,偶尔出现回调
。。。看来比较玄学。。。问题又一次陷入了僵局
逐个看代码
xxx_sessionWithConfiguration对应的一段代码吸引了我
xxx_sessionWithConfiguration是用来替换系统方法sessionWithConfiguration,这里刚好与前期没有回调的方法匹配
,看来问题大概率就在这里了
xxx_sessionWithConfiguration大概意思是创建了一proxy的实例,将系统原生的delegate进行封装,而后进行传递
,按照控制变量法,将封装去掉,看看回调是否成功
成功了!
看来就是这了
接着看proxy和delegate之间的联系
proxy是nsproxy的子类
通过一个弱引用,引用delegate
这里一个知识点:nsproxy作为代理更为轻量,因为
-
NSProxy
是一个抽象的基类,是根类,与NSObject
类似 -
NSProxy
和NSObject
都实现了<NSObject>
协议
NSObject的所有Category中定义的方法无法在继承NSObject的代理中完成转发,具体参考
Redirecting...
这里使用了nsproxy主要在转发时,进行http截取,然后再进行真正的转发
既然做转发,就在每次转发时打印一下log,看看有没有回到原始的delegate
结果比较诧异!
proxy引用的delegate变为空!
怪不得delegate没反应
现在的情况是变成
proxy--->delegate 时 delegate为空
dalagate单独使用,可以正常使用
查看一下proxy里面的代码,确认没有置空的操作
所以问题变成了delegate怎么没的
按照内存爱好者的常用思想,弱引用是否有问题,其实proxy一般来说使用弱引用,这样可以解决一些定时器等循环引用的问题
问题又一次陷入了僵局
😂
最后,不死心试下将proxy--->delegate 的弱引用变成强引用
成功了!
什么原因呢?
再分析一次
proxy--->delegate 弱引用时 delegate被置空
proxy——>delegate 强引用时 delegate有效,可以正常使用
dalagate单独使用,可以正常使用
proxy里面没有置空delegate的操作
所以,谁置空了delegate!
推理大师说过,除所有不可能的,剩下的那个即使再不可思议,那也是事实
不要怀疑,是的,这是一个系统bug!!!
NSProxy在ARC下,弱引用的属性,会被强制置空,不要怀疑,亲测有效
Weak references to NSProxy with ARC - Joris Kluivers
https://oomake.com/question/2426222
大家可以尝试一下,刺激。。。
拓展知识1:
对于http的截取分析,有两个思路,一个是AFNetWork的思路,通过获取代理和HOOK resume 和suspend的方式,一种是https://www.jianshu.com/p/ae5e8f9988d8的方法,其实两种方式各有利弊,AFN的方法是大部分第三方库的方式,“紧贴”系统方法,与系统交互较多,能够更多的进行性能分析,而后面这种思路“紧贴”业务,可以对前后端的数据进行业务级过滤,重定向等,看具体业务需要
拓展知识2:
关于网络监控ios版,有些比较优秀的可以参考
https://www.jianshu.com/p/3bdb027a63c7