天天看点

FBRetainCycleDetector不能扫描__block变量的问题分析和解决方案问题描述FBRetainCycleDetector的原理简介回归问题解决方案git路径

这还得从使用aspects这个库说起,如下图:

FBRetainCycleDetector不能扫描__block变量的问题分析和解决方案问题描述FBRetainCycleDetector的原理简介回归问题解决方案git路径

上图中的id token 的类型如下图:

FBRetainCycleDetector不能扫描__block变量的问题分析和解决方案问题描述FBRetainCycleDetector的原理简介回归问题解决方案git路径

aspectidentifier 的block属性对应的是图一的传参usingblock,很明显token引用了usingblock,usingblock引用了token,构成循环引用,这个是一个常规的循环引用内存泄漏。可是fbretaincycledetector没有扫描出环,一开始我怀疑是我自己改造过的allocationtracker没有跟踪到aspectidentifier实例(闲鱼改造了fballocationtracker),经排查allocationtracker是有记录的,可是为什么没有扫描出环呢?

1.记录下运行过程中创建和释放的实例

2.分析未释放的实例retain了哪些实例(对应深度优先遍历的临接表),逐层展开,形成一个有向图

3.使用深度优先遍历图的过程中,查看是否有成环

一般的oc对象获取retain的实例通过运行时接口很容易就可以获取到,block就比较特殊,下面介绍一下fbretaincycledetector 如何获取block捕获的变量,并确定哪些变量是被block强引用的:

对应的接口 static nsindexset _getblockstronglayout(void block)

FBRetainCycleDetector不能扫描__block变量的问题分析和解决方案问题描述FBRetainCycleDetector的原理简介回归问题解决方案git路径

判断block是否强引用其他变量并拷贝至堆上:

FBRetainCycleDetector不能扫描__block变量的问题分析和解决方案问题描述FBRetainCycleDetector的原理简介回归问题解决方案git路径

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)

FBRetainCycleDetector不能扫描__block变量的问题分析和解决方案问题描述FBRetainCycleDetector的原理简介回归问题解决方案git路径
FBRetainCycleDetector不能扫描__block变量的问题分析和解决方案问题描述FBRetainCycleDetector的原理简介回归问题解决方案git路径
FBRetainCycleDetector不能扫描__block变量的问题分析和解决方案问题描述FBRetainCycleDetector的原理简介回归问题解决方案git路径
FBRetainCycleDetector不能扫描__block变量的问题分析和解决方案问题描述FBRetainCycleDetector的原理简介回归问题解决方案git路径
FBRetainCycleDetector不能扫描__block变量的问题分析和解决方案问题描述FBRetainCycleDetector的原理简介回归问题解决方案git路径

现在回归最开始那个问题,从图一可以知道usingblock捕获了3个变量,

FBRetainCycleDetector不能扫描__block变量的问题分析和解决方案问题描述FBRetainCycleDetector的原理简介回归问题解决方案git路径

只有disposal、guidehandler,没有__block id token

对比一下三者的区别,只有token是__block声明过(这里已经确定token没有被释放,查看allocationtracker记录是否有token变量即可),说明fb获取block捕获变量的方案对__block无效

这是我声明的一个block:

FBRetainCycleDetector不能扫描__block变量的问题分析和解决方案问题描述FBRetainCycleDetector的原理简介回归问题解决方案git路径

对其执行clang -rewrite-objc编译转换成c++实现

FBRetainCycleDetector不能扫描__block变量的问题分析和解决方案问题描述FBRetainCycleDetector的原理简介回归问题解决方案git路径
FBRetainCycleDetector不能扫描__block变量的问题分析和解决方案问题描述FBRetainCycleDetector的原理简介回归问题解决方案git路径

从上图可以确定block捕获的变量就存放在block的abi struct 的末尾;__block声明的变量要复杂一些,isa、forwarding、flags、size是必有,disposer、copy是obj-c对象才有

来看看block的disposer和copy函数实现:

FBRetainCycleDetector不能扫描__block变量的问题分析和解决方案问题描述FBRetainCycleDetector的原理简介回归问题解决方案git路径

分别对捕获的变量做了相应的disposer和copy处理,第二个参数传人的值是不一样的,估计就是这个参数有关联

FBRetainCycleDetector不能扫描__block变量的问题分析和解决方案问题描述FBRetainCycleDetector的原理简介回归问题解决方案git路径

看一下红色框:这里直接跳过了block的头部,做了一个小优化,直接从存放变量的索引开始

1.是否是malloc指针:不做这步过滤,会导致指针的强转取值crash,因为block可能捕获了一个inter数据,这时如果当成指针来处理,那么基本上会被当作访问非法指针而崩溃

2.去掉__block变量没有disposer和copy 函数的变量,这个使用size大小来判断即可

FBRetainCycleDetector不能扫描__block变量的问题分析和解决方案问题描述FBRetainCycleDetector的原理简介回归问题解决方案git路径
FBRetainCycleDetector不能扫描__block变量的问题分析和解决方案问题描述FBRetainCycleDetector的原理简介回归问题解决方案git路径
FBRetainCycleDetector不能扫描__block变量的问题分析和解决方案问题描述FBRetainCycleDetector的原理简介回归问题解决方案git路径

上面每一个构造赋值都是必不可少的

FBRetainCycleDetector不能扫描__block变量的问题分析和解决方案问题描述FBRetainCycleDetector的原理简介回归问题解决方案git路径

diposer() 释放了 struct blockbyref 下的void *refobj对象(即fbblockstrongrelationdetector对象)

2.这里注意一下,为什么没有对malloc出来的的__block变量进行free?这里调用free的话,会导致crash,因为block的disposer函数调用到了__block变量自己的disposer接口的时候,已经释放了这段内存,我是通过打开malloc scribble 这个调试开关,发现这段内存有部分被置为了0x55确定的。

FBRetainCycleDetector不能扫描__block变量的问题分析和解决方案问题描述FBRetainCycleDetector的原理简介回归问题解决方案git路径

从图片可以看出来__block变量已然被解析出来了

我把修改提交到fbretaincycledetector的github上的一个个人分支,等待fb的省核,附上git地址:

<a href="https://github.com/chaokongzwp/fbretaincycledetector.git">https://github.com/chaokongzwp/fbretaincycledetector.git</a>

继续阅读