FBRetainCycleDetector是Facebook新開源的一個項目。配合FBMemoryProfiler使用起來也是很方便。當然FBMemoryProfiler里面使用到了FBAllocationTracker。目前第一版,在測試的過程中也會遇到一些crash,相信經(jīng)過使用者的修改和作者本人的自測,會越來越完善的。這篇文章的目的主要是對于FBRetainCycleDetector內(nèi)部實現(xiàn)進行一個介紹,單單只會使用總感覺是遠遠不夠的。
文章會分為幾個模塊進行介紹:
<p id="最簡單的使用方法">
最簡單的使用方法
最簡單的使用方法,不包含Configuration。單純的去查找一個對象的引用循環(huán)
FBRetainCycleDetector *detector = [[FBRetainCycleDetector alloc] initWithConfiguration:nil];
[detector addCandiate:myObject];
//- (NSSet<NSArray<FBObjectiveCGraphElement *> *> *)findRetainCycles;
NSSet<NSArray<FBObjectiveCGraphElement *> *> *retainCycles = [detector findRetainCycles];
NSLog(@"%@", retainCycles);
這里先簡單的說明一下,findRetainCycles查詢方式所使用到的算法是DFS(深度優(yōu)先搜索)。
<p id="主要元素類及其輔助類的介紹">
主要元素類及其輔助類的介紹(FBObjectiveCGraphElement)
FBRetainCycleDetector所使用到的對象類型是FBObjectiveCGraphElement,會在調用函數(shù):addCandiate的時候內(nèi)部進行初始化為該對象類型或者其子類。
FBObjectiveCGraphElement
FBObjectiveCGraphElement是所有用來查找對象類型的基類。所有的查找對象都基于它實現(xiàn)。該類并不需要外部的調用,主要是供內(nèi)部查詢使用。其提供的功能主要是:
- 提供初始化方法封裝
object(即調用addCandiate傳入的object) - 獲取所有該對象所持有對象
- (NSSet *)allRetainedObjects;?;?code>FBObjectiveCGraphElement所獲取的對象類型是通過associated object所持有的對象。 associated object對象的獲取是通過Facebook自身的fishhook去hook原先的objc_setAssociatedObject和objc_removeAssociatedObjects來實現(xiàn)對象的持有標記。 - 提供過濾接口
- (NSSet *)filterObjects:(nullable NSArray *)objects;,過濾接口主要是與FBObjectGraphConfiguration相結合使用,FBObjectGraphConfiguration會在下文介紹。 - 以及其它一些helper接口,例如:獲取類、類名、地址等等。
FBObjectGraphConfiguration
這里先介紹一下Configuration,再去介紹FBObjectiveCGraphElement的子類。FBObjectGraphConfiguration內(nèi)容很少,其主要提供的是過濾的block類型FBGraphEdgeFilterBlock和過濾器的初始化方法:
- (instancetype)initWithFilterBlocks:(NSArray<FBGraphEdgeFilterBlock> *)filterBlocks
shouldInspectTimers:(BOOL)shouldInspectTimers
即傳入一個過濾block的數(shù)組,該數(shù)組會被FBObjectiveCGraphElement對象類型在調用filterObjects的時候一次調用。shouldInspectTimers的作用是是否檢查NSTimer。
接下來看看FBGraphEdgeFilterBlock的定義:
typedef FBGraphEdgeType (^FBGraphEdgeFilterBlock)(FBObjectiveCGraphElement *_Nullable fromObject,
FBObjectiveCGraphElement *_Nullable toObject);
傳入fromObject(傳入的對象)和toObject(被持有的對象),根據(jù)自己需求對對象進行處理。添加到數(shù)組后進行初始化。這里可以舉個例子,過濾掉所有以UINavi開頭的對象:
FBGraphEdgeFilterBlock filterBlock = ^(FBObjectiveCGraphElement *_Nullable fromObject,
FBObjectiveCGraphElement *_Nullable toObject){
if (![[fromObject classNameOrNull] hasPrefix:@"UINavi"]) {
return FBGraphEdgeValid;
}
return FBGraphEdgeInvalid;
};
FBObjectGraphConfiguration *configuration = [[FBObjectGraphConfiguration alloc]
initWithFilterBlocks:@[filterBlock]
shouldInspectTimers:NO];
FBRetainCycleDetector *detector = [[FBRetainCycleDetector alloc] initWithConfiguration:configuration];
這就是一個包含configuration的初始化過程。
FBObjectiveCGraphElement相關子類
上面已經(jīng)說了,F(xiàn)BObjectiveCGraphElement只提供了對associate object的持有查找。因此其它對象的持有查找是通過子類實現(xiàn)的,主要包含:FBObjectiveCBlock,FBObjectiveCObject,FBObjectiveCNSCFTimer
FBObjectiveCBlock實現(xiàn)
主要的實現(xiàn)內(nèi)容是:重寫父類方法allRetainedObjects,當然也是有調用[super allRetainedObjects]。接下來就是對于block的識別和獲取引用關系。最后再封裝為FBObjectiveCBlock對象類型。
block識別方法的一些細節(jié):
用到FBBlockStrongLayout.h里面的函數(shù)(用C實現(xiàn))來進行判斷是不是block以及獲取引用。判斷是不是block所用的方法是利用一個空block^{},判斷傳入的block是不是其子類。當然這里是先轉換為Class類型。block內(nèi)部引用關系獲取:
獲取引用關系相對就比較發(fā)雜一點,先通過block的size_t大小創(chuàng)建相同大小的數(shù)組類型,其對象類型是FBBlockStrongRelationDetector,該對象主要是為了統(tǒng)計block內(nèi)部內(nèi)容是否是一個對象,會在release的時候進行標記。接下來對于每一個調用該block的dispose_helper,如若調用了release則證明其是對象,否則就是一些普通數(shù)據(jù)類型。記錄其在block內(nèi)部的位置關系,返回位置關系數(shù)組。再通過數(shù)組獲取每一個對象。最后當然也會調用
filterObjects過濾掉不需要查找引用循環(huán)的對象。
FBObjectiveCObject實現(xiàn)
在重寫以及調用父類方法與block是一樣的。不同的地方在于對于持有對象的獲取。。
- FBClassStrongLayout里面的函數(shù)輔助獲取對象,同樣利用到了runtime(
class_copyIvarList)機制去獲取屬性列表,并且封裝為FBIvarReference類型。如果遇到屬性是struct類型還需單獨進行處理。 -
FBIvarReference的初始化方法會對不同的Ivar進行分類:FBObjectType,FBBlockType,FBStructType,FBUnknownType. - 然后也是會封裝成
FBObjectiveCObject,在未過濾之前,需要判斷該object的類型。因為object的有些類型在進行數(shù)據(jù)處理的時候會造成崩潰,目前fb處理了一部分,但經(jīng)過測試還是會發(fā)生異常的崩潰現(xiàn)象,不過這個現(xiàn)象主要是對于系統(tǒng)的一些對象類型遍歷所造成的。暫時沒有發(fā)現(xiàn)自己創(chuàng)建的對象在查找retain cycle的時候發(fā)生崩潰。
FBObjectiveCNSCFTimer實現(xiàn)
FBObjectiveCNSCFTimer的實現(xiàn)內(nèi)容比較少,其主要就是通過runloop去獲取CFRunLoopTimerGetContext,再對獲取到的數(shù)據(jù)進行處理即可。
<p id="主要的查找類及其輔助類介紹">
主要的查找類及其輔助類介紹(FBRetainCycleDetector)
FBRetainCycleDetector
-
FBRetainCycleDetector主要的功能就是查找retain cycle。使用到的算法思想是深度優(yōu)先搜索(DFS),因此如果在對象量比較大并且查找深度(默認為8)比較深的情況下,會比較慢。一般情況下是在異步線程執(zhí)行查找。 -
FBRetainCycleDetector會對通過方法addCandidate所添加的對象都進行DFS,當然查找之前會通過FBObjectGraphConfiguration進行過濾。
其中查找過程對對象會進一步的封裝為FBNodeEnumerator類型,接下來介紹該類型。
FBNodeEnumerator
-
FBNodeEnumerator繼承于NSEnumerator,NSEnumerator可以方便的提供nextObject的方法調用,只需在子類中重寫該方法即可。 -
FBNodeEnumerator中的nextObject主要的處理是:通過object去獲取allRetainedObjects(此方法是FBObjectiveCGraphElement提供獲取過濾后的持有對象)。再獲取第一個對象進行返回。 - 至于深搜的一些數(shù)據(jù)存儲,這里就不進行解釋。
結論
FBRetainCycleDetector目前處于第一版本,因此會有一些bug,但并不會影響正常的使用。雖然查找算法上面有可能會導致比較大的內(nèi)存消耗(畢竟如果程序夠大的話,深搜也是談不上效率的)。暫時沒有對FBMemoryProfiler進行描述的原因是,FBMemoryProfiler主要還是界面的實現(xiàn)以及與FBAllocationTracker功能的結合。 FBAllocationTracker的功能比較簡單,后面會用一篇小文章來進行概述。