MLeaksFinder 分析4:深入细节
前言
在MLeaksFinder
中,大多数文件只是系统类的category
,使用Method Swizzling
,提供了一个发送泄漏检查的时机。实际上真正检查内存泄漏的两个类是MLeakedObjectProxy
和MLeaksMessenger
。前者负责数据逻辑,后者负责UI。
MLeakedObjectProxy
MLeakedObjectProxy对外有两个方法
+ (BOOL)isAnyObjectLeakedAtPtrs:(NSSet *)ptrs;
+ (void)addLeakedObject:(id)object;
第一个方法用来判断ptrs
(NSSet
类型)中是否有泄漏的对象,如果有返回True
第二个方法是将对象加入泄漏对象的集合,同时调用MLeaksMessenger的弹窗方法
无论是判断还是比较,始终需要一个集合来保存所有泄漏对象。自然而然检查MLeakedObjectProxy
,
看到一个全局static变量static NSMutableSet *leakedObjectPtrs;
,它就是用来做比较的对象。
上面两个方法都只在 NSObject的category的assertNotDealloc
中调用
- (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]);
}
显然方法二的调用前提是方法一为True。初始化状态下,leakedObjectPtrs中没有元素,[MLeakedObjectProxy isAnyObjectLeakedAtPtrs:[self parentPtrs]]
为NO,执行[MLeakedObjectProxy addLeakedObject:self]
后
+ (void)addLeakedObject:(id)object {
NSAssert([NSThread isMainThread], @"Must be in main thread.");
MLeakedObjectProxy *proxy = [[MLeakedObjectProxy alloc] init];
proxy.object = object;
proxy.objectPtr = @((uintptr_t)object);
proxy.viewStack = [object viewStack];
static const void *const kLeakedObjectProxyKey = &kLeakedObjectProxyKey;
objc_setAssociatedObject(object, kLeakedObjectProxyKey, proxy, OBJC_ASSOCIATION_RETAIN);
[leakedObjectPtrs addObject:proxy.objectPtr];
}
其中 [leakedObjectPtrs addObject:proxy.objectPtr]
将自己封装成 MLeakedObjectProxy对象加入leakedObjectPtrs中。再回头看看isAnyObjectLeakedAtPtrs的实现
+ (BOOL)isAnyObjectLeakedAtPtrs:(NSSet *)ptrs {
NSAssert([NSThread isMainThread], @"Must be in main thread.");
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
leakedObjectPtrs = [[NSMutableSet alloc] init];
});
if (!ptrs.count) {
return NO;
}
if ([leakedObjectPtrs intersectsSet:ptrs]) {
return YES;
} else {
return NO;
}
}
会由于 [self ptrs]
构成的 nsset
和leakedObjectPtrs
对象有交集返回True
,从而防止 addLeakedObject
多次被调用,这里[self ptrs]
的具体意义下一篇讲。
MLeaksMessenger
MLeaksMessenger
是一个工具类,用来弹内存泄漏的视图框。只有两个方法
+ (void)alertWithTitle:(NSString *)title message:(NSString *)message
+ (void)alertWithTitle:(NSString *)title
message:(NSString *)message
delegate:(id<UIAlertViewDelegate>)delegate
additionalButtonTitle:(NSString *)additionalButtonTitle
前者调用后者只是delegate
和additionalButtonTitle
传nil
.后者的实现如下
static __weak UIAlertView *alertView;
+ (void)alertWithTitle:(NSString *)title
message:(NSString *)message
delegate:(id<UIAlertViewDelegate>)delegate
additionalButtonTitle:(NSString *)additionalButtonTitle {
[alertView dismissWithClickedButtonIndex:0 animated:NO];
UIAlertView *alertViewTemp = [[UIAlertView alloc] initWithTitle:title
message:message
delegate:delegate
cancelButtonTitle:@"OK"
otherButtonTitles:additionalButtonTitle, nil];
[alertViewTemp show];
alertView = alertViewTemp;
NSLog(@"%@: %@", title, message);
}
这里有一个小技巧,使用静态全局变量用__weak
修饰变量。第一次调用这个方法的时候,alertView
为nil
, [alertView dismissWithClickedButtonIndex:0 animated:NO]
不产生任何操作,只是一个弹框。再次调用这个方法,会通过alertView来dimiss现有的弹框,再显示新的弹框。所以alertView
是记录当前显示的内存泄漏的弹框。同时设置__weak
修饰让这个全局变量弱引用。一旦弹框消失,自动设置为nil
.