PLeakSniffer

原理分析在这里:http://www.cocoachina.com/ios/20160706/16951.html
我的代码分析:
入口函数,在application:didFinishLaunchingWithOptions中

   if (DEBUG) {
        [[PLeakSniffer sharedInstance] installLeakSniffer];
    }

创建单例,进入到

- (void)installLeakSniffer {
    [UINavigationController prepareForSniffer];
    [UIViewController prepareForSniffer];
    [UIView prepareForSniffer];
    
    [self startPingTimer];
}

先调用各个类的prepareForSniffer方法,然后启动定时器,每隔0.5s发送一个Notif_PLeakSniffer_Ping的通知。
先分析UIViewController的prepareForSniffer方法

+ (void)prepareForSniffer{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self swizzleSEL:@selector(presentViewController:animated:completion:) withSEL:@selector(swizzled_presentViewController:animated:completion:)];
        [self swizzleSEL:@selector(viewDidAppear:) withSEL:@selector(swizzled_viewDidAppear:)];
    });
}

- (void)swizzled_presentViewController:(UIViewController *)viewControllerToPresent animated: (BOOL)flag completion:(void (^)(void))completion{
    [self swizzled_presentViewController:viewControllerToPresent animated:flag completion:completion];
    // modal出控制器时候执行
    [viewControllerToPresent markAlive];
}

交换方法,hook住控制器被modal的那种形态,此外UINavigationController的prepareForSniffer其实就是hook住控制器被push的那种形态,总之会确保控制器无论以哪种形式出现,都能在出现那一刻调用markAlive方法。

// 控制器、View、Model等都会进入此处
- (BOOL)markAlive
{
    if ([self pProxy] != nil) {
        return false;
    }
    
    // 过滤系统自带的类
    NSString* className = NSStringFromClass([self class]);
    if ([className hasPrefix:@"_"] || [className hasPrefix:@"UI"] || [className hasPrefix:@"NS"]) {
        return false;
    }
    
    // 对View类型,必需保证父视图是存在的
    if ([self isKindOfClass:[UIView class]]) {
        UIView* v = (UIView*)self;
        if (v.superview == nil) {
            return false;
        }
    }
    
    // 对控制器,需保证该控制器的容器栈存在
    if ([self isKindOfClass:[UIViewController class]]) {
        UIViewController* c = (UIViewController*)self;
        if (c.navigationController == nil && c.presentingViewController == nil) {
            return false;
        }
    }
    
    // 忽略类型,例如不需要监控的单例对象
    static NSMutableDictionary* ignoreList = nil;
    @synchronized (self) {
        if (ignoreList == nil) {
            ignoreList = @{}.mutableCopy;
            NSArray* arr = @[@"UITextFieldLabel", @"UIFieldEditor", @"UITextSelectionView",
                             @"UITableViewCellSelectedBackground", @"UIView", @"UIAlertController"];
            for (NSString* str in arr) {
                ignoreList[str] = @":)";
            }
        }
        if ([ignoreList objectForKey:NSStringFromClass([self class])]) {
            return false;
        }
    }
    
    // 创建一个PObjectProxy对象,将其绑定到自身
    PObjectProxy* proxy = [PObjectProxy new];
    [self setPProxy:proxy];
    
    // 让proxy的target属性指向自身,注意target是一个weak对象,避免循环引用
    [proxy prepareProxy:self];
    
    return true;
}

PObjectProxy对象主要是监听PLeakSniffer单例不断发出的通知,不断调用以下方法

- (void)detectSnifferPing{
    if (self.weakTarget == nil) return;
    if (_hasNotified) return;
    // 不断调用self.weakTarget属性指向对象的isAlive方法,若连续返回五次false,则认为可能存在内存泄漏
    BOOL alive = [self.weakTarget isAlive];
    if (alive == false) {
        _leakCheckFailCount ++;
    }
    if (_leakCheckFailCount >= kPObjectProxyLeakCheckMaxFailCount) {
        [self notifyPossibleMemoryLeak];
    }
}

方法的关键是如何判断target指向对象是否销毁,关键方法是isAlive的实现。
以下是UIViewController的isAlive方法

- (BOOL)isAlive{
    BOOL alive = true;
    BOOL visibleOnScreen = false;

    UIView* v = self.view;
    while (v.superview != nil) { // 找到该控制器对象View的顶层视图
        v = v.superview;
    }
    if ([v isKindOfClass:[UIWindow class]]) {
        visibleOnScreen = true;
    }
    
    BOOL beingHeld = false;
    if (self.navigationController != nil || self.presentingViewController != nil) {
        beingHeld = true;
    }
    
    // visibleOnScreen为NO,说明该控制器视图已经不在当前窗口之上
    // beingHeld为NO,说明该控制器已经被pop或者dismiss
    // 两个条件都是NO,说明该控制器已经被pop或者dismiss,且不在窗口之上,但是尚未销毁,存在内存泄漏
    if (visibleOnScreen == false && beingHeld == false) {
        alive = false;
    }
    return alive;
}

对控制器来说,控制器拥有PObjectProxy对象,若控制器销毁,则PObjectProxy对象会被销毁,不能再监听来自单例PLeakSniffer的通知。若控制器未销毁,则PObjectProxy会不断监听通知,调用控制器的isAlive方法判断是否存在内存泄漏的可能,若有,则打印输出。这里对控制器内存泄漏的规则是:控制器已经被pop或者dismiss,且控制器的视图已不在窗口之上。

同理,对View来说, hook替换didMoveToSuperview方法,对每一个视图添加到父视图上时给它绑定PObjectProxy对象。

- (BOOL)isAlive{
    BOOL alive = true;
    BOOL onUIStack = false;
    
    // 同控制器,判断视图是否在当前窗口之上
    UIView* v = self;
    while (v.superview != nil) {
        v = v.superview;
    }
    if ([v isKindOfClass:[UIWindow class]]) {
        onUIStack = true;
    }
    
    if (self.pProxy.weakResponder == nil) {
        UIResponder* r = self.nextResponder;
        while (r) {
            if (r.nextResponder == nil) {
                break;
            }else{
                r = r.nextResponder;
            }
            if ([r isKindOfClass:[UIViewController class]]) {
                break;
            }
        }
       // 该view的属性.pProxy.weakResponder弱引用了所属的控制器
        self.pProxy.weakResponder = r; 
    }

    // 若指向的控制器销毁了,self.pProxy.weakResponder会为nil
    if (onUIStack == false && ![self.pProxy.weakResponder isKindOfClass:[UIViewController class]]) {
        alive = false;
    }
    return alive;
}

UIView要想显示只能添加在控制器视图或UIWindow上。前者通过nextResponder查找,找到则使用弱指针指向该控制器(只需查找一次)。 后者通过superView递归查找顶层视图,如果某个时刻发现控制器不存在,且顶层视图不是UIWindow,那么判断存在泄露

用此框架,发现了一个第三方轮播图框架HYBLoopScrollView存在内存泄漏,仔细查看发现是setPageControlEnabled方法block中使用了self导致。。。

你可能感兴趣的:(PLeakSniffer)