美文网首页
PLeakSniffer

PLeakSniffer

作者: 请叫我啊亮 | 来源:发表于2017-11-09 11:45 被阅读32次

    原理分析在这里: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来说

    - (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;
    }
    

    对View来说,判断其可能存在内存泄漏的规则是:其所属的控制器已经销毁,且该视图不在当前窗口之上。

    还有控制器所定义的属性。。。。套路差不多

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

    相关文章

      网友评论

          本文标题:PLeakSniffer

          本文链接:https://www.haomeiwen.com/subject/ookpmxtx.html