首先,MLeaksFinder的核心代碼都在NSObject+MemoryLeak類中,咱們將從該類開始分析MLeaksFinder的源碼。bash
- (BOOL)willDealloc;
- (void)willReleaseObject:(id)object relationship:(NSString *)relationship;
- (void)willReleaseChild:(id)child;
- (void)willReleaseChildren:(NSArray *)children;
- (NSArray *)viewStack;
+ (void)addClassNamesToWhitelist:(NSArray *)classNames;
+ (void)swizzleSEL:(SEL)originalSEL withSEL:(SEL)swizzledSEL;
複製代碼
該分類提供了以上7個重要方法,供其餘分類使用,歷來完成對內存泄露監控和堆棧信息的處理。ide
- (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;//執行target-action的時候,目標對象不檢測內存泄漏。
__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];//若是兩秒後對象釋放,strongSelf爲nil,對nil發消息則不執行內存泄露檢測。
});
return YES;
}
複製代碼
這裏再說一下target-action,對於一個給定的事件,UIControl會調用sendAction:to:forEvent:來將行爲消息轉發到UIApplication對象,再由UIApplication對象調用其sendAction:to:fromSender:forEvent:方法來將消息分發到指定的target上,而若是咱們沒有指定target,則會將事件分發到響應鏈上第一個想處理消息的對象上。而若是子類想監控或修改這種行爲的話,則能夠重寫這個方法。在UIApplication+MemoryLeak.m中利用swizzling方法調配技術,將sendAction:to:from:forEvent:方法截獲,從而利用關聯對象的方法objc_setAssociatedObject在swizzled_sendAction:to:from:forEvent:中將sender保存起來。上面三四行代碼就是將sender利用關聯對象方法取出,將self轉行爲無符號長整形與sender對比,若是相同則返回NO,從而忽略對正在執行action的對象的內存泄露檢測。函數
最後幾行代碼利用了OC對空指針發送消息不會奔潰的特性完成了對內存泄露是否泄露的判斷,緣由是OC的函數調用都是經過objc_msgSend進行消息派發來實現的,而objc_msgSend會經過判斷self來決定是否發送消息,若是self爲nil,那麼selector也會爲空,直接返回,因此不會出現問題。學習
- (void)assertNotDealloc {
if ([MLeakedObjectProxy isAnyObjectLeakedAtPtrs:[self parentPtrs]]) {
return;
}//
[MLeakedObjectProxy addLeakedObject:self];
NSString *className = NSStringFromClass([self class]);
NSLog(@"Possibly Memory Leak.\nIn case that %@ should not be dealloced, override -willDealloc in %@ by returning NO.\nView-ViewController stack: %@", className, className, [self viewStack]);
}
複製代碼
isAnyObjectLeakedAtPtrs方法查看NSSet是否有交集,是的話返回YES。否的話,返回NO。不在交集中,則要addLeakedObject方法添加到泄漏名單之中,並顯示alert彈窗名單。ui
- (NSArray *)viewStack {
NSArray *viewStack = objc_getAssociatedObject(self, kViewStackKey);
if (viewStack) {
return viewStack;
}
NSString *className = NSStringFromClass([self class]);
return @[ className ];
}
- (void)setViewStack:(NSArray *)viewStack {
objc_setAssociatedObject(self, kViewStackKey, viewStack, OBJC_ASSOCIATION_RETAIN);
}
- (NSSet *)parentPtrs {
NSSet *parentPtrs = objc_getAssociatedObject(self, kParentPtrsKey);
if (!parentPtrs) {
parentPtrs = [[NSSet alloc] initWithObjects:@((uintptr_t)self), nil];
}
return parentPtrs;
}
複製代碼
上面的四個方法,至關於實現了兩個屬性變量的getter和setter方法,如爲空會給getter方法添加一個self默認元素。spa
- (void)willReleaseChild:(id)child {
if (!child) {
return;
}
[self willReleaseChildren:@[ child ]];
}
- (void)willReleaseChildren:(NSArray *)children {
NSArray *viewStack = [self viewStack];
NSSet *parentPtrs = [self parentPtrs];
for (id child in children) {
NSString *className = NSStringFromClass([child class]);
[child setViewStack:[viewStack arrayByAddingObject:className]];
[child setParentPtrs:[parentPtrs setByAddingObject:@((uintptr_t)child)]];
[child willDealloc];
}
}
複製代碼
上面的方法爲構建堆棧信息的方法,遍歷子控件去檢查是否泄露,泄露的話就添加到泄露名單中leakedObjectPtrs。指針