问题描述
这还得从使用Aspects这个库说起,如下图:
上图中的id token 的类型如下图:
AspectIdentifier 的block属性对应的是图一的传参usingBlock,很明显token引用了usingblock,usingblock引用了token,构成循环引用,这个是一个常规的循环引用内存泄漏。可是FBRetainCycleDetector没有扫描出环,一开始我怀疑是我自己改造过的AllocationTracker没有跟踪到AspectIdentifier实例(闲鱼改造了FBAllocationTracker),经排查AllocationTracker是有记录的,可是为什么没有扫描出环呢?
FBRetainCycleDetector的原理简介
1.记录下运行过程中创建和释放的实例
2.分析未释放的实例retain了哪些实例(对应深度优先遍历的临接表),逐层展开,形成一个有向图
3.使用深度优先遍历图的过程中,查看是否有成环
一般的oc对象获取retain的实例通过运行时接口很容易就可以获取到,block就比较特殊,下面介绍一下FBRetainCycleDetector 如何获取block捕获的变量,并确定哪些变量是被block强引用的:
对应的接口 static NSIndexSet _GetBlockStrongLayout(void block)
0x01:转换block对应的ABI 结构,判断是否存在block强引用的变量
判断block是否强引用其他变量并拷贝至堆上:
Block引用了 1) C++ 栈上对象 2)OC对象 3) 其他block对象 4) __block修饰的变量,并被拷贝至堆上时则需要copy/dispose辅助函数,辅助函数由编译器生成
除了case 1,其他三种case都会分别调用下面的函数:
void _Block_object_assign(void destAddr, const void object, const int flags);
void _Block_object_dispose(const void *object, const int flags);
_Block_object_assign(或者_Block_object_dispose)会根据flags的值来决定调用相应类型的copy helper(或者dispose helper)
0x02:根据block size确定捕获了多少个变量,创建出一个同样大小的block,并且给捕获的变量指针指向一个伪装的oc对象FBBlockStrongRelationDetector
0x03:调用block的dispose_helper()释放伪装的block
0x04:查看dispose_helper()调用了哪几个FBBlockStrongRelationDetector的 release接口
0x05:根据前面记录的索引下标,记录下真正被block捕获并强引用的实例
回归问题
现在回归最开始那个问题,从图一可以知道usingblock捕获了3个变量,
0x01看看fb的分析结果:
只有disposal、guideHandler,没有__block id token
对比一下三者的区别,只有token是__block声明过(这里已经确定token没有被释放,查看AllocationTracker记录是否有token变量即可),说明fb获取block捕获变量的方案对__block无效
0x02对比一下差别:
这是我声明的一个block:
对其执行clang -rewrite-objc编译转换成C++实现
从上图可以确定block捕获的变量就存放在block的ABI struct 的末尾;__block声明的变量要复杂一些,isa、forwarding、flags、size是必有,disposer、copy是obj-c对象才有
来看看block的disposer和copy函数实现:
分别对捕获的变量做了相应的disposer和copy处理,第二个参数传人的值是不一样的,估计就是这个参数有关联
解决方案
0x01 按原有的方式先把block捕获的非__block声明的strong变量找出来
看一下红色框:这里直接跳过了block的头部,做了一个小优化,直接从存放变量的索引开始
0x02 除去第一步找到的变量,对剩余的变量在做两次次过滤:
1.是否是malloc指针:不做这步过滤,会导致指针的强转取值crash,因为block可能捕获了一个inter数据,这时如果当成指针来处理,那么基本上会被当作访问非法指针而崩溃
2.去掉__block变量没有disposer和copy 函数的变量,这个使用size大小来判断即可
0x03 构造__block变量
上面每一个构造赋值都是必不可少的
0x04 再一次调用block的disposer函数,对构造的block进行释放操作,获取最后的结果
- diposer() 释放了 struct BlockByref 下的void *refObj对象(即FBBlockStrongRelationDetector对象)
2.这里注意一下,为什么没有对malloc出来的的__block变量进行free?这里调用free的话,会导致crash,因为block的disposer函数调用到了__block变量自己的disposer接口的时候,已经释放了这段内存,我是通过打开malloc scribble 这个调试开关,发现这段内存有部分被置为了0x55确定的。
查看结果
从图片可以看出来__block变量已然被解析出来了
git路径
我把修改提交到FBRetainCycleDetector的github上的一个个人分支,等待fb的省核,附上git地址:
https://github.com/chaokongzwp/FBRetainCycleDetector.git