FBRetainCycleDetector分析

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_setAssociatedObjectobjc_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繼承于NSEnumeratorNSEnumerator可以方便的提供nextObject的方法調用,只需在子類中重寫該方法即可。
  • FBNodeEnumerator中的nextObject主要的處理是:通過object去獲取allRetainedObjects(此方法是FBObjectiveCGraphElement提供獲取過濾后的持有對象)。再獲取第一個對象進行返回。
  • 至于深搜的一些數(shù)據(jù)存儲,這里就不進行解釋。

結論

FBRetainCycleDetector目前處于第一版本,因此會有一些bug,但并不會影響正常的使用。雖然查找算法上面有可能會導致比較大的內(nèi)存消耗(畢竟如果程序夠大的話,深搜也是談不上效率的)。暫時沒有對FBMemoryProfiler進行描述的原因是,FBMemoryProfiler主要還是界面的實現(xiàn)以及與FBAllocationTracker功能的結合。 FBAllocationTracker的功能比較簡單,后面會用一篇小文章來進行概述。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 國家電網(wǎng)公司企業(yè)標準(Q/GDW)- 面向對象的用電信息數(shù)據(jù)交換協(xié)議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 12,355評論 6 13
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,539評論 19 139
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內(nèi)部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,642評論 18 399
  • 這篇文章完全是基于南峰子老師博客的轉載 這篇文章完全是基于南峰子老師博客的轉載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,885評論 33 466
  • 在中國,端午節(jié)有不少傳統(tǒng)習俗,比如,賽龍舟、吃粽子、佩香囊等等。粽子,又叫“角黍”、“筒粽”。其由來已久,花樣繁多...
    樽伯活竹酒閱讀 635評論 0 0

友情鏈接更多精彩內(nèi)容