FBRetainCycleDetector中獲取block強(qiáng)引用的對(duì)象實(shí)現(xiàn)方式
在我的上一篇文章中介紹了如何獲取block捕獲的對(duì)象庙洼,思路是通過解析block內(nèi)部的layout簽名串顿痪。最近看FBRetainCycleDetector源碼時(shí)發(fā)現(xiàn)它用了一種十分巧妙的方式獲取,第一見到時(shí)我也被這種新奇的方式驚艷到油够,下面就開始正文看下它是如何做的蚁袭。
獲取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 *);
// 計(jì)算一個(gè)block的size能夠存放多少指針大小的數(shù)據(jù)石咬,向上取整
const size_t elements = (blockLiteral->descriptor->size + ptrSize - 1) / ptrSize;
// 通過數(shù)組構(gòu)造一個(gè)虛擬block
void *obj[elements];
//保存detector對(duì)象
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ù)揩悄,這個(gè)obj就是手動(dòng)構(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這個(gè)標(biāo)識(shí)位查看block是否有析構(gòu)函數(shù)鬼悠,如果沒有則代表block沒有捕獲對(duì)象直接返回nil删性。如果有析構(gòu)函數(shù)則獲取到block中的析構(gòu)函數(shù)指針,這個(gè)析構(gòu)函數(shù)的作用主要就是對(duì)block中捕獲的強(qiáng)引用對(duì)象調(diào)用release方法焕窝。然后后面獲取到block的size镇匀,并計(jì)算這個(gè)size能存放多少個(gè)指針大小的數(shù)據(jù),64位系統(tǒng)下也即是size/8向上取整的結(jié)果袜啃。后面就到了整個(gè)實(shí)現(xiàn)最巧妙的地方了,首先是創(chuàng)建了兩個(gè)數(shù)組汗侵,obj和detectors,這兩個(gè)數(shù)組的大小和block的大小是一致的群发,然后用FBBlockStrongRelationDetector對(duì)象填充這兩個(gè)數(shù)組晰韵,然后調(diào)用block的析構(gòu)函數(shù)dispose_helper(obj),可以看到這里是將obj數(shù)組傳進(jìn)去了熟妓,所以這里obj就是一個(gè)手動(dòng)構(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是捕獲了兩個(gè)強(qiáng)引用對(duì)象和兩個(gè)int類型變量雪猪。那么通過數(shù)組構(gòu)造的那個(gè)虛擬block如下所示
我說過dispose_helper方法是對(duì)block捕獲的對(duì)象調(diào)用release方法,所以上圖中紅框圈出的部分會(huì)調(diào)用release起愈,也即是[detctor release];detctor是FBBlockStrongRelationDetector類型只恨,我們來看看這個(gè)類的實(shí)現(xiàn)。
注意到它重寫了release方法抬虽,并且在release調(diào)用的時(shí)候?qū)strong設(shè)置為YES官觅,也就是說dispose_helper(obj)調(diào)用的時(shí)候圖1中index為4,5的detector對(duì)象會(huì)進(jìn)入到release方法并標(biāo)記為strong阐污。
如此就找到了強(qiáng)引用對(duì)象休涤,后面就是遍歷detectors數(shù)組,然后將strong == YES的對(duì)象篩選出來并保存。最后因?yàn)橹貙懥藃elease方法功氨,對(duì)象并沒有真正釋放序苏,還需要調(diào)用trueRelease進(jìn)行收尾工作。
不得不說這是一個(gè)非常巧妙的方法捷凄,不僅需要對(duì)block的底層結(jié)構(gòu)非常了解忱详,還需要對(duì)內(nèi)存分布有著充分了解。但是這種方法仍然有著局限性跺涤,例如 __block id obj = [NSObject new];這種通過__block修飾的對(duì)象并不能獲取匈睁,因?yàn)開_block修飾的變量底層轉(zhuǎn)成了byref的結(jié)構(gòu)體變量,雖然其也有isa指針钦铁,但是實(shí)際它的isa是設(shè)置為0软舌,它并不是一個(gè)NSObject類型的對(duì)象,也就不會(huì)調(diào)用到release方法牛曹。但在實(shí)際開發(fā)中這種__block id obj形式非常少見佛点,所以也無傷大雅。