FBRetainCycleDetector 是干什么的?
FBRetainCycleDetector 是facebook 開源的,用於檢測引起內存泄漏對象的環形引用鏈。
MLeakFinder 是干什么的?
MLeakFinder 是檢測內存中可能發生了內存泄漏的對象。
為什么檢測內存泄漏是 FBRetainCycleDetector + MLeaksFinder 的組合?
Facebook 的 FBRetainCycleDetector 是針對可疑對象的引用結構進行樹形檢測,繪制引用圖,可設置遍歷的深度,默認是10,可設置更大的遍歷深度,整個操作是比較費時的,因此不應該頻繁調用,而是有針對性的去使用。而 MLeakFinder 則恰恰是用於找出內存中可能發生內存泄口的可疑對象,MLeakFinder 的操作相對簡單,且並不會十分消耗性能。因此經常是FBRetainCycleDetector + MLeaksFinder 的組合使用。
MLeaksFinder 是如何工作的?
閱讀從 MLeaksFinder 的源碼,找到其中比較核心的部分,來簡要說明MLeaksFinder 的工作原理:MLeaksFinder 中有幾個比較重要的 category 是 MLeaksFinder work的入口,在 UINavigationController+MemoryLeak.h category +load 方法中hook了
- (void)pushViewController:animated:
- (void)popViewControllerAnimated:popToViewController:animated:
- (void)popToRootViewControllerAnimated:
系統方法, 在方法中去標明一個VC是否應該被釋放,在vc被 pop,dismiss 的時候。標志一個 vc 被 “droped”,對於需要延遲釋放的 打上 “checkDelay” 的標志,下一次 push 動作時再檢測UIViewController+MemoryLeak.h 的 + load 方法中 hook 了
- (void)viewWillAppear:
- (void)viewDidDisappear:
- (void)dismissViewControllerAnimated:completion:
生命周期方法, 在 viewWillAppear 時標記自身 “unDroped”, dismissViewControllerAnimated:completion: 時,標志自身 “droped” viewDidDisappear 時檢測自身的 “drop”標志,如果獲取到droped 標志,則開始檢測自身的內存泄漏問題。
因為,當一個頁面退出時並且消失時, 我們認為這個頁面應該被釋放,如果有需要緩存,下次再行使用的頁面 可以不進行檢測。
那到底如何檢測自身呢,在MLeaksFinder 中 有幾個基礎的類的自檢方式,分別為 UIViewController, UIView , NSObject , 這幾個主要的類,這幾個基礎類名為 MemoryLeak 的 category 都會實現一個 willDealloc 方法 。 在 VC 的viewDidDisappear 的時候去調用VC本身的willDealloc 方法([super willDealloc ]),VC的view([self.view willDealloc])的 willDealloc 方法。UIView 也會同時檢測subviews 的 willDealloc。這樣則能檢測頁面的model 層, UI 層 的內存泄漏。UIView , UIViewController 都繼承自 NSObject,最終的調用實際上都會走到NSObject 的 willDealloc 方法中。而NSObject 的 willDealloc 方法實現為:
- (BOOL)willDealloc {
NSString *className = NSStringFromClass([self class]);
if ([[NSObject classNamesWhitelist] containsObject:className]) return NO;
NSNumber *senderPtr = objc_getAssociatedObject([UIApplication sharedApplication], kLatestSenderKey);
if ([senderPtr isEqualToNumber:@((uintptr_t)self)]) return NO;
__weak id weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
__strong id strongSelf = weakSelf;
[strongSelf assertNotDealloc];
});
return YES;
}
其中最為重要的一段為:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
__strong id strongSelf = weakSelf;
[strongSelf assertNotDealloc];
});
這個延遲執行的代碼是很有心機的,正常頁面退出2s后,如果沒有內存泄漏,頁面的VC,和View 都將會釋放,此時 strongSelf 的引用都會變成nil,OC中給nil發消息([strongSelf assertNotDealloc])是不響應的。如果響應了說明還沒有釋放,那么將成為可疑對象。需要“嚴加盤問”。
那么 assertNotDealloc 方法 如何嚴加盤問呢?
此時 MLeaksFinder 是不負責問責,是在哪個環節可能發生了內存泄漏的,因此給開發者以彈框的形式,提示當前對象可能存在內存泄漏情況。點擊 “Retain Cycle” 按鈕,MLeaksFinder 將調用 FBRetainCycleDetector 進行詳細問題檢測。
FBRetainCycleDetector 如何檢測引用成環?
FBRetainCycleDetector 基於外部傳入的object 以及查找深度,進行深度優先遍歷所有強引用屬性,和動態運行時關聯的強引用屬性,同時將這些 關聯對象的地址 放入 objectSet (set)的集合中, 將對象信息計入 objectOnPath 集合中(set), 並且將對象在對象棧 stack 中存儲一份,便於查找對應環。stack 的長度代表了當前遍歷的深度。首先判斷 如果傳入的 object 是 NSObject 的話,獲取對象的 class,使用
const char *class_getIvarLayout(Class cls);
獲取 class 的所有定義的 property 的布局信息,取出 object 對應的 property 的value值,將value 對象的地址(數字)加入 objectSet 中,將對象指針加入到 objectOnPath,在整個樹形遍歷中,如果遍歷到的新節點,在原來的 objectSet 地址表中已經存在了,代表形成了引用環,即原本的樹形結構連成了圖。此時可以根據 stack中記錄的路徑,結合 重復的 object構建一個環形圖,作為環形引用鏈返回。但是,遇到的是NSBlock類型對像,我們首先要知道的是NSBlock在內存中怎么存儲的,因此FBRetainCycleDetector 參考了Clang 的文檔,對於block的結構定義如下:
struct Block_literal_1 { void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock int flags; int reserved; void (*invoke)(void *, ...); struct Block_descriptor_1 { unsigned long int reserved; // NULL unsigned long int size; // sizeof(struct Block_literal_1) // optional helper functions void (*copy_helper)(void *dst, void *src); // IFF (1<<25) void (*dispose_helper)(void *src); // IFF (1<<25) // required ABI.2010.3.16 const char *signature; // IFF (1<<30) } *descriptor; // imported variables };
因此,FBRetainCycleDetector 庫中定義了一個和block 結構一致的 struct 名為BlockLiteral,將遇到的block都強轉為BlockLiteral,便可以操作block對應的屬性和方法BlockLiteral 定義如下:
enum { // Flags from BlockLiteral BLOCK_HAS_COPY_DISPOSE = (1 << 25), BLOCK_HAS_CTOR = (1 << 26), // helpers have C++ code BLOCK_IS_GLOBAL = (1 << 28), BLOCK_HAS_STRET = (1 << 29), // IFF BLOCK_HAS_SIGNATURE BLOCK_HAS_SIGNATURE = (1 << 30), }; struct BlockDescriptor { unsigned long int reserved; // NULL unsigned long int size; // optional helper functions void (*copy_helper)(void *dst, void *src); // IFF (1<<25) void (*dispose_helper)(void *src); // IFF (1<<25) const char *signature; // IFF (1<<30) }; struct BlockLiteral { void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock int flags; int reserved; void (*invoke)(void *, ...); struct BlockDescriptor *descriptor; // imported variables };
雖然知道了block對象的存儲結構,知道了在block中哪里記錄引用 但哪些對象的存儲是強引用,我們任然不知道,在C的結構體中是不存在強弱引用區分的,在編譯期,編譯器會將所謂的強引用通過一個 copy_helper 的function 做copy 操作,並為 block 生成的 struct 構造 dispose_helper 的 function,dispose_helper 負責在 struct 將要釋放時,去釋放它所引用的對象。下面是編譯器生成的 dispose_helper function 的定義 ,入參為 struct 的地址 _Block_object_dispose 是編譯器的 funtion
void __block_dispose_4(struct __block_literal_4 *src) { // was _Block_destroy _Block_object_dispose(src->existingBlock, BLOCK_FIELD_IS_BLOCK); }
於是 FBRetainCycleDetector 作者想到利用黑盒測試,基於原有的 block對象 ,拿到對應block對象的 descriptor指針 ,descriptor記錄了block對象釋放的時候要執行的 dispose_helper 方法和block對象所有引用對象的數組,
這個數組包括了強引用對象和弱應用對象 *src。 也就是說,block被釋放時,執行的 dispose_helper 方法的入參 是 *scr;那么只需要偽裝一個被引用的數組,傳入dispose_helper 做個測試,數組中哪一個對象唄調用了 release 方法,那么誰就是被強引用的,記住src對應下標的地址就好。
查找代碼如下:
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; } //獲取block引用描述對象 void (*dispose_helper)(void *src) = blockLiteral->descriptor->dispose_helper; const size_t ptrSize = sizeof(void *); // 被引用對象指針數組的長度. const size_t elements = (blockLiteral->descriptor->size + ptrSize - 1) / ptrSize; // 偽造被引用的指針數組 void *obj[elements]; void *detectors[elements]; for (size_t i = 0; i < elements; ++i) { FBBlockStrongRelationDetector *detector = [FBBlockStrongRelationDetector new]; obj[i] = detectors[i] = detector; } //傳入偽造的引用的指針數組,執行析構函數,看數組中的哪些對象會被執行release方法,執行的結果在detectors數組中會被記錄 @autoreleasepool { dispose_helper(obj); } //將探測結果中的強引用過濾出來,返回 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; }
。算法操作草圖如下圖(手稿圖。。。。尷尬。。。):

檢測核心代碼:
NSMutableSet<NSArray<FBObjectiveCGraphElement *> *> *retainCycles = [NSMutableSet new];
FBNodeEnumerator *wrappedObject = [[FBNodeEnumerator alloc] initWithObject:graphElement];
NSMutableArray<FBNodeEnumerator *> *stack = [NSMutableArray new];
NSMutableSet<FBNodeEnumerator *> *objectsOnPath = [NSMutableSet new];
[stack addObject:wrappedObject];
while ([stack count] > 0) {
@autoreleasepool {
FBNodeEnumerator *top = [stack lastObject];
if (![objectsOnPath containsObject:top]) {
if ([_objectSet containsObject:@([top.object objectAddress])]) {
[stack removeLastObject];
continue;
}
[_objectSet addObject:@([top.object objectAddress])];
}
[objectsOnPath addObject:top];
//獲取子節點迭代器
FBNodeEnumerator *firstAdjacent = [top nextObject];
if (firstAdjacent) {
//有子節點
BOOL shouldPushToStack = NO;
//當前鏈路上已存在當前子節點
if ([objectsOnPath containsObject:firstAdjacent]) {
NSUInteger index = [stack indexOfObject:firstAdjacent];
NSInteger length = [stack count] - index;
if (index == NSNotFound) {
shouldPushToStack = YES;
} else {
//構建環結構
NSRange cycleRange = NSMakeRange(index, length);
NSMutableArray<FBNodeEnumerator *> *cycle = [[stack subarrayWithRange:cycleRange] mutableCopy];
[cycle replaceObjectAtIndex:0 withObject:firstAdjacent];
[retainCycles addObject:[self _shiftToUnifiedCycle:[self _unwrapCycle:cycle]]];
}
} else {
shouldPushToStack = YES;
}
if (shouldPushToStack) {
if ([stack count] < stackDepth) {
[stack addObject:firstAdjacent];
}
}
} else {
//無子節點
[stack removeLastObject];
[objectsOnPath removeObject:top];
}
}
}
return retainCycles;
作者:10m每秒滑行
鏈接:https://www.jianshu.com/p/76250de94b93
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。