FBRetainCycleDetector中获取block强引用
FBRetainCycleDetector中获取block强引用的对象实现方式
在我的上一篇文章中介绍了如何获取block捕获的对象,思路是通过解析block内部的layout签名串。最近看FBRetainCycleDetector源码时发现它用了一种十分巧妙的方式获取,第一见到时我也被这种新奇的方式惊艳到,下面就开始正文看下它是如何做的。
获取block强引用的相关代码在FBBlockStrongLayout.m中的static NSIndexSet *_GetBlockStrongLayout(void *block)函数中,如下所示:
static NSIndexSet *_GetBlockStrongLayout(void *block) {
struct BlockLiteral *blockLiteral = block;
/**
BLOCK_HAS_CTOR - Block has a C++ constructor/destructor, which gives us a good chance it retains
objects that are not pointer aligned, so omit them.
!BLOCK_HAS_COPY_DISPOSE - Block doesn't have a dispose function, so it does not retain objects and
we are not able to blackbox it.
*/
if ((blockLiteral->flags & BLOCK_HAS_CTOR)
|| !(blockLiteral->flags & BLOCK_HAS_COPY_DISPOSE)) {
return nil;
}
void (*dispose_helper)(void *src) = blockLiteral->descriptor->dispose_helper;
const size_t ptrSize = sizeof(void *);
// 计算一个block的size能够存放多少指针大小的数据,向上取整
const size_t elements = (blockLiteral->descriptor->size + ptrSize - 1) / ptrSize;
// 通过数组构造一个虚拟block
void *obj[elements];
//保存detector对象
void *detectors[elements];
for (size_t i = 0; i < elements; ++i) {
FBBlockStrongRelationDetector *detector = [FBBlockStrongRelationDetector new];
obj[i] = detectors[i] = detector;
}
@autoreleasepool {
//调用block的析构函数,这个obj就是手动构造的虚拟block
dispose_helper(obj);
}
// Run through the release detectors and add each one that got released to the object's
// strong ivar layout.
NSMutableIndexSet *layout = [NSMutableIndexSet indexSet];
for (size_t i = 0; i < elements; ++i) {
FBBlockStrongRelationDetector *detector = (FBBlockStrongRelationDetector *)(detectors[i]);
if (detector.isStrong) {
[layout addIndex:i];
}
// Destroy detectors
[detector trueRelease];
}
return layout;
}
首先是将block强转成struct BlockLiteral类型的block底层结构,然后通过BLOCK_HAS_COPY_DISPOSE这个标识位查看block是否有析构函数,如果没有则代表block没有捕获对象直接返回nil。如果有析构函数则获取到block中的析构函数指针,这个析构函数的作用主要就是对block中捕获的强引用对象调用release方法。然后后面获取到block的size,并计算这个size能存放多少个指针大小的数据,64位系统下也即是size/8向上取整的结果。后面就到了整个实现最巧妙的地方了,首先是创建了两个数组,obj和detectors,这两个数组的大小和block的大小是一致的,然后用FBBlockStrongRelationDetector对象填充这两个数组,然后调用block的析构函数dispose_helper(obj),可以看到这里是将obj数组传进去了,所以这里obj就是一个手动构造的虚拟block,我们先来假设有这这样一段代码
NSObject *obj_strong1 = [NSObject new];
NSObject *obj_strong2 = [NSObject new];
int aVal = 10;
int bVal = 20;
void (^aBlock)(void) = ^{
[obj_strong1 description];
[obj_strong2 description];
int c = aVal + bVal;
};
可以看出aBlock是捕获了两个强引用对象和两个int类型变量。那么通过数组构造的那个虚拟block如下所示
图1我说过dispose_helper方法是对block捕获的对象调用release方法,所以上图中红框圈出的部分会调用release,也即是[detctor release];detctor是FBBlockStrongRelationDetector类型,我们来看看这个类的实现。
图2注意到它重写了release方法,并且在release调用的时候将_strong设置为YES,也就是说dispose_helper(obj)调用的时候图1中index为4,5的detector对象会进入到release方法并标记为strong。
如此就找到了强引用对象,后面就是遍历detectors数组,然后将strong == YES的对象筛选出来并保存。最后因为重写了release方法,对象并没有真正释放,还需要调用trueRelease进行收尾工作。
不得不说这是一个非常巧妙的方法,不仅需要对block的底层结构非常了解,还需要对内存分布有着充分了解。但是这种方法仍然有着局限性,例如 __block id obj = [NSObject new];这种通过__block修饰的对象并不能获取,因为__block修饰的变量底层转成了byref的结构体变量,虽然其也有isa指针,但是实际它的isa是设置为0,它并不是一个NSObject类型的对象,也就不会调用到release方法。但在实际开发中这种__block id obj形式非常少见,所以也无伤大雅。