首先,MLeaksFinder的核心代码都在NSObject+MemoryLeak类中,我们将从该类开始分析MLeaksFinder的源码。
- (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个重要方法,供其他分类使用,从来完成对内存泄露监控和堆栈信息的处理。
- (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弹窗名单。
- (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默认元素。
- (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。