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