FBRetainCycleDetector中獲取block強(qiáng)引用的對象實現(xiàn)方式
在我的上一篇文章中介紹了如何獲取block捕獲的對象,思路是通過解析block內(nèi)部的layout簽名串。最近看FBRetainCycleDetector源碼時發(fā)現(xiàn)它用了一種十分巧妙的方式獲取,第一見到時我也被這種新奇的方式驚艷到,下面就開始正文看下它是如何做的。
獲取block強(qiáng)引用的相關(guān)代碼在FBBlockStrongLayout.m中的static NSIndexSet *_GetBlockStrongLayout(void *block)函數(shù)中,如下所示:
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能夠存放多少指針大小的數(shù)據(jù),向上取整
const size_t elements = (blockLiteral->descriptor->size + ptrSize - 1) / ptrSize;
// 通過數(shù)組構(gòu)造一個虛擬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 {
//調(diào)用block的析構(gòu)函數(shù),這個obj就是手動構(gòu)造的虛擬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強(qiáng)轉(zhuǎn)成struct BlockLiteral類型的block底層結(jié)構(gòu),然后通過BLOCK_HAS_COPY_DISPOSE這個標(biāo)識位查看block是否有析構(gòu)函數(shù),如果沒有則代表block沒有捕獲對象直接返回nil。如果有析構(gòu)函數(shù)則獲取到block中的析構(gòu)函數(shù)指針,這個析構(gòu)函數(shù)的作用主要就是對block中捕獲的強(qiáng)引用對象調(diào)用release方法。然后后面獲取到block的size,并計算這個size能存放多少個指針大小的數(shù)據(jù),64位系統(tǒng)下也即是size/8向上取整的結(jié)果。后面就到了整個實現(xiàn)最巧妙的地方了,首先是創(chuàng)建了兩個數(shù)組,obj和detectors,這兩個數(shù)組的大小和block的大小是一致的,然后用FBBlockStrongRelationDetector對象填充這兩個數(shù)組,然后調(diào)用block的析構(gòu)函數(shù)dispose_helper(obj),可以看到這里是將obj數(shù)組傳進(jìn)去了,所以這里obj就是一個手動構(gòu)造的虛擬block,我們先來假設(shè)有這這樣一段代碼
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是捕獲了兩個強(qiáng)引用對象和兩個int類型變量。那么通過數(shù)組構(gòu)造的那個虛擬block如下所示

我說過dispose_helper方法是對block捕獲的對象調(diào)用release方法,所以上圖中紅框圈出的部分會調(diào)用release,也即是[detctor release];detctor是FBBlockStrongRelationDetector類型,我們來看看這個類的實現(xiàn)。

注意到它重寫了release方法,并且在release調(diào)用的時候?qū)strong設(shè)置為YES,也就是說dispose_helper(obj)調(diào)用的時候圖1中index為4,5的detector對象會進(jìn)入到release方法并標(biāo)記為strong。
如此就找到了強(qiáng)引用對象,后面就是遍歷detectors數(shù)組,然后將strong == YES的對象篩選出來并保存。最后因為重寫了release方法,對象并沒有真正釋放,還需要調(diào)用trueRelease進(jìn)行收尾工作。
不得不說這是一個非常巧妙的方法,不僅需要對block的底層結(jié)構(gòu)非常了解,還需要對內(nèi)存分布有著充分了解。但是這種方法仍然有著局限性,例如 __block id obj = [NSObject new];這種通過__block修飾的對象并不能獲取,因為__block修飾的變量底層轉(zhuǎn)成了byref的結(jié)構(gòu)體變量,雖然其也有isa指針,但是實際它的isa是設(shè)置為0,它并不是一個NSObject類型的對象,也就不會調(diào)用到release方法。但在實際開發(fā)中這種__block id obj形式非常少見,所以也無傷大雅。