牛叉的demo

MLeaksFinder 分析4:深入细节

2017-10-21  本文已影响15人  dc630f46ee2d

前言

MLeaksFinder中,大多数文件只是系统类的category,使用Method Swizzling,提供了一个发送泄漏检查的时机。实际上真正检查内存泄漏的两个类是MLeakedObjectProxyMLeaksMessenger。前者负责数据逻辑,后者负责UI。

MLeakedObjectProxy

MLeakedObjectProxy对外有两个方法

+ (BOOL)isAnyObjectLeakedAtPtrs:(NSSet *)ptrs;
+ (void)addLeakedObject:(id)object;

第一个方法用来判断ptrsNSSet类型)中是否有泄漏的对象,如果有返回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]构成的 nssetleakedObjectPtrs对象有交集返回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

前者调用后者只是delegateadditionalButtonTitlenil.后者的实现如下

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修饰变量。第一次调用这个方法的时候,alertViewnil, [alertView dismissWithClickedButtonIndex:0 animated:NO]不产生任何操作,只是一个弹框。再次调用这个方法,会通过alertView来dimiss现有的弹框,再显示新的弹框。所以alertView是记录当前显示的内存泄漏的弹框。同时设置__weak修饰让这个全局变量弱引用。一旦弹框消失,自动设置为nil.

上一篇下一篇

猜你喜欢

热点阅读