IOS内存泄漏检查方法及重写MLeakFinder

对于iOS开发来讲,内存泄漏的问题,已经是老生常谈的话题。在日常的面试中经常会提到这些问题。我们日常的开发过程中进行内存泄漏的检测,一般是使用instrument工具中的Leaks/Allocation来进行排查,网络上也有比较高效又好用的内存泄漏检测工具,MLeakFinder。

MLeakFinder-原理

首先看UIViewController,当一个UIViewController被pop或dismiss的时候,这个VC包括在这个VC上的View,或者子View都会很快的被释放。所以我们我们需要在UIViewController被POP或dismiss后一小段时间后,在这个VC上的view,subView等是否还存在。

在UIViewController+MemoryLeak.h的load方法中可以看到,早+load方法中通过runtime交换了viewWillAppear,viewDidAppear,dismissViewControllerAnimated:completion:这三个方法。

1,首先看viewWillAppear

- (void)swizzled_viewWillAppear:(BOOL)animated {
    [self swizzled_viewWillAppear:animated];
    objc_setAssociatedObject(self, kHasBeenPoppedKey, @(NO), OBJC_ASSOCIATION_RETAIN);
}

当VC进来的时候,添加关联对象,并标记为NO

2,在看viewDidAppear

- (void)swizzled_viewDidDisappear:(BOOL)animated {
    [self swizzled_viewDidDisappear:animated];
    if ([objc_getAssociatedObject(self, kHasBeenPoppedKey) boolValue]) {
        [self willDealloc];
    }
}

通过代码可以看出,获取当前关联对象的标记,当标记为YES的时候,就会调用willDealloc。

3,我们看什么时候会被标记为YES呢?

在UINavigationController+MemoryLeak.h的popViewControllerAnimated:方法中我们可以看到

- (UIViewController *)swizzled_popViewControllerAnimated:(BOOL)animated {
    UIViewController *poppedViewController = [self swizzled_popViewControllerAnimated:animated];
    if (!poppedViewController) {
        return nil;
    }
// Detail VC in UISplitViewController is not dealloced until another detail VC is shown
    if (self.splitViewController &&
        self.splitViewController.viewControllers.firstObject == self &&
        self.splitViewController == poppedViewController.splitViewController) {
        objc_setAssociatedObject(self, kPoppedDetailVCKey, poppedViewController, OBJC_ASSOCIATION_RETAIN)
        return poppedViewController;    }    // VC is not dealloced until disappear when popped using a left-edge swipe gesture
    extern const void *const kHasBeenPoppedKey;
    objc_setAssociatedObject(poppedViewController, kHasBeenPoppedKey, @(YES), OBJC_ASSOCIATION_RETAIN);
    return poppedViewController;
}

我们可以看出,在VC被pop或者左滑返回的时候,相当于视图销毁,就会被标记为YES。

4,我们重点看willDealloc

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

   //第三步
    __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];
    });
    return YES;
}

1.第一步:我们可以看到,会先判断当前的class是否在白名单中,是的话就会return NO,即不是内存泄漏的。
同时我们查看构建白名单的源码:使用了一个单例实现,确保只有一个,是个私有方法

+ (NSMutableSet *)classNamesWhitelist {
    static NSMutableSet *whitelist = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        whitelist = [NSMutableSet setWithObjects:
                     @"UIFieldEditor", // UIAlertControllerTextField
                     @"UINavigationBar",
                     @"_UIAlertControllerActionView",
                     @"_UIVisualEffectBackdropView",
                     nil];

        // System's bug since iOS 10 and not fixed yet up to this ci.
        NSString *systemVersion = [UIDevice currentDevice].systemVersion;
        if ([systemVersion compare:@"10.0" options:NSNumericSearch] != NSOrderedAscending) {
            [whitelist addObject:@"UISwitch"];
        }
    });
    return whitelist;
}

同时在还支持,自定义的添加白名单

+ (void)addClassNamesToWhitelist:(NSArray *)classNames {
    [[self classNamesWhitelist] addObjectsFromArray:classNames];
}

2. 第二步:判断该对象是否是上一次发送action的对象,是的话,不进行内存检测

 //第二步

 NSNumber *senderPtr = objc_getAssociatedObject([UIApplication sharedApplication], kLatestSenderKey)  

if ([senderPtr isEqualToNumber:@((uintptr_t)self)])

return NO;

3,第三步:弱指针指向self,2s延迟,然后通过这个弱指针调用-assertNotDealloc,若被释放,给nil发消息直接返回,不触发-assertNotDealloc方法,认为已经释放;如果它没有被释放(泄漏了),-assertNotDealloc就会被调用

    __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];
    });

5,现在我们回到:2的代码 [self willDealloc]

看一下他的源码

- (BOOL)willDealloc {
    //第一步
    if (![super willDealloc]) {
        return NO;
    }
    //第二步
    [self willReleaseChildren:self.childViewControllers];
    [self willReleaseChild:self.presentedViewController];
    if (self.isViewLoaded) {
        [self willReleaseChild:self.view];
    }
    return YES;
}

1,第一步:会通过 super调用父类的willDealloc,即上面目录4

2,第二步:调用willReleaseChildren,willReleaseChild遍历该对象的子对象,看其是否释放

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

通过代码可以看出,通过调用willReleaseChildren的方法,获取当前对象viewStack,parentPtrs,并且遍历children,为每个子对象设置viewStack,parentPtrs,然后调用willDealloc。

通过源码看一下viewStask,parentPtrs的实现

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

- (void)setParentPtrs:(NSSet *)parentPtrs {
    objc_setAssociatedObject(self, kParentPtrsKey, parentPtrs, OBJC_ASSOCIATION_RETAIN);
}

viewStack使用数组,parentPtrs使用的集合形式。都是通过运行时,用关联对象添加属性。

parentPtrs会在-assertNotDealloc中,会判断当前对象是否与父节点集合有交集。下面仔细看下-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]);
}

1,第一步我们看到,通过parentPtrs的判断是否有交集

产看其源码:

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

可以看到,创建了一个单例对象,通过集合的形式,判断是否有交集,是的话return。否则就进入第二步

2,第二步:addLeakedObject

+ (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];
#if _INTERNAL_MLF_RC_ENABLED    [MLeaksMessenger alertWithTitle:@"Memory Leak"                            message:[NSString stringWithFormat:@"%@", proxy.viewStack]
                           delegate:proxy
              additionalButtonTitle:@"Retain Cycle"];
#else
    [MLeaksMessenger alertWithTitle:@"Memory Leak"
                            message:[NSString stringWithFormat:@"%@", proxy.viewStack]];#endif
}

第一步:构造MLeakedObjectProxy对象,给传入的泄漏对象 object 关联一个代理即 proxy

第二步:通过objc_setAssociatedObject(object, kLeakedObjectProxyKey, proxy, OBJC_ASSOCIATION_RETAIN)方法,object强持有proxy, proxy若持有object,如果object释放,proxy也会释放

第三步:存储 proxy.objectPtr(实际是对象地址)到集合 leakedObjectPtrs 里边

第四步:弹框 AlertView若 _INTERNAL_MLF_RC_ENABLED == 1,则弹框会增加检测循环引用的选项;若 _INTERNAL_MLF_RC_ENABLED == 0,则仅展示堆栈信息。

对于MLeakedObjectProxy类而言,是检测到内存泄漏才产生的,作为泄漏对象的属性存在的,如果泄漏的对象被释放,那么MLeakedObjectProxy也会被释放,则调用-dealloc函数

集合leakedObjectPtrs中移除该对象地址,同时再次弹窗,提示该对象已经释放了

6,自己也在尝试重写该框架,欢迎大家一起交流

以上就是IOS内存泄漏检查方法及重写MLeakFinder的详细内容,更多关于IOS内存泄漏的资料请关注我们其它相关文章!

(0)

相关推荐

  • iOS WKWebView中MessageHandler内存泄漏问题的完美解决过程

    背景 项目中使用了WKWebView替换了之前的UIWebView,牵扯到Hybird开发,我们需要和H5交互,所以用到了WKWebViewConfiguration 中的 WKUserContentController 所以初始化代码如下 WKUserContentController *userContentController = [[WKUserContentController alloc] init]; [userContentController addScriptMessageH

  • iOS中wkwebView内存泄漏与循环引用问题详解

    前言 现在大多数网络也面加载都会用到wkwebview,之前在使用wkwebview的时候,网上很多的基础教程使用很多只是说了怎么添加Message Handler 但是并没有告诉到家有这个内存泄漏的风险,如果你只是也没内的数据调用你压根都不会发现这个问题.没存泄漏这个问题说大不大,说小不小,严重的话话直接到时app闪退,所以还是得重视起.好下面说一下怎么解决,话不多说了,来一起看看详细的介绍吧 解决方法 1,在做网页端js交互的时候 我们都会这样去添加js [self.customWebVie

  • IOS 常见内存泄漏以及解决方案

    IOS 常见内存泄漏以及解决方案 整理了几个内存泄漏的例子,由于转载地址已经找不到了,在这里就不一一列出来了. 1 OC和CF转化出现的内存警告 CFStringRef cfString = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,(CFStringRef)picDataString,NULL,CFSTR(":/?#[]@!$&'()*+,;="),kCFStringEncodingUTF8); N

  • IOS内存泄漏检查方法及重写MLeakFinder

    对于iOS开发来讲,内存泄漏的问题,已经是老生常谈的话题.在日常的面试中经常会提到这些问题.我们日常的开发过程中进行内存泄漏的检测,一般是使用instrument工具中的Leaks/Allocation来进行排查,网络上也有比较高效又好用的内存泄漏检测工具,MLeakFinder. MLeakFinder-原理 首先看UIViewController,当一个UIViewController被pop或dismiss的时候,这个VC包括在这个VC上的View,或者子View都会很快的被释放.所以我们

  • C++程序检测内存泄漏的方法分享

    一.前言 在Linux平台上有valgrind可以非常方便的帮助我们定位内存泄漏,因为Linux在开发领域的使用场景大多是跑服务器,再加上它的开源属性,相对而言,处理问题容易形成"统一"的标准.而在Windows平台,服务器和客户端开发人员惯用的调试方法有很大不同.下面结合我的实际经验,整理下常见定位内存泄漏的方法. 注意:我们的分析前提是Release版本,因为在Debug环境下,通过VLD这个库或者CRT库本身的内存泄漏检测函数能够分析出内存泄漏,相对而言比较简单.而服务器有很多问

  • Android中LeakCanary检测内存泄漏的方法

    最近要对产品进行内存泄漏的检查,最后选择了使用Square公司开源的一个检测内存泄漏的函数库LeakCanary,在github上面搜索了一下竟然有1.6w个star,并且Android大神JakeWharton也是这个开源库的贡献者.那么就赶快拿来用吧. 先说一下我遇到的坑,我当时是直接google的,然后就直接搜索到稀土掘金的一篇关于LeakCanary的介绍,我就按照他们的文章一步步的操作,到最后才发现,他们那个build.gradle中导入的库太老了,会报这样的错误Closed Fail

  • Qt下监测内存泄漏的方法

    在写Qt应用程序时,由于是采用C++语言,经常会碰到一个令人棘手的问题,那就是内存泄漏,虽然后面C++为了防止内存泄漏,发布了智能指针以用来避免内存泄漏,但是并不能完全避免.而且智能指针使用不当,同样会造成非常严重的问题.这里智能指针就不在赘述,有机会我再写篇智能指针的博客.     为了预防内存泄漏问题,我们手动写一个宏,用来监测该类是否析构,若未析构,则将该类打印出来,这样可以更好的定位问题.代码如下: 定义一个帮助类,用来计数,当注册的类声明时,计数+1,当类析构时,计数-1,若在该类析构

  • Go map发生内存泄漏解决方法

    目录 正文 hamp 结构体代码 查看占用的内存数量 对于 map 内存泄漏的解法 正文 Go 程序运行时,有些场景下会导致进程进入某个“高点”,然后就再也下不来了. 比如,多年前曹大写过的一篇文章讲过,在做活动时线上涌入的大流量把 goroutine 数抬升了不少,流量恢复之后 goroutine 数也没降下来,导致 GC 的压力升高,总体的 CPU 消耗也较平时上升了 2 个点左右. 有一个 issue 讨论为什么 allgs(runtime 中存储所有 goroutine 的一个全局 sl

  • Erlang项目内存泄漏分析方法

    随着项目越来越依赖Erlang,碰到的问题也随之增加.前段时间线上系统碰到内存高消耗问题,记录一下troubleshooting的分析过程.线上系统用的是Erlang R16B02版本. 问题描述 有几台线上系统,运行一段时间,内存飙升.系统模型很简单,有网络连接,pool中找新的process进行处理.top命令观察,发现内存都被Erlang进程给吃完了,netstat命令查看网络连接数,才区区几K.问题应该是Erlang内存泄漏了. 分析方法 Erlang系统有个好处,可以直接进入线上系统,

  • PHPExcel内存泄漏问题解决方法

    使用 PHPExcel 来生成 excel 文档是比较消耗内存的,有时候可能会需要通过一个循环来把大数据切分成若干个小的 excel 文档保存来避免内存耗尽. 然而 PHPExcel 存在 circular references 的情况(貌似在最新的 1.6.5 版本中仍然没有去解决这个问题),如果在一次 http 请求过程中反复多次构建 PHPExcel 及 PHPExcel_Writer_Excel5 对象实例来完成多个 excel 文档生成操作的话,所有被构建的对象实例都无法在 http

  • Android 内存泄漏的几种可能总结

    Java是垃圾回收语言的一种,其优点是开发者无需特意管理内存分配,降低了应用由于局部故障(segmentation fault)导致崩溃,同时防止未释放的内存把堆栈(heap)挤爆的可能,所以写出来的代码更为安全. 不幸的是,在Java中仍存在很多容易导致内存泄漏的逻辑可能(logical leak).如果不小心,你的Android应用很容易浪费掉未释放的内存,最终导致内存用光的错误抛出(out-of-memory,OOM). 一般内存泄漏(traditional memory leak)的原因

  • 详解Android内存泄漏检测与MAT使用

    内存泄漏基本概念 内存检测这部分,相关的知识有JVM虚拟机垃圾收集机制,类加载机制,内存模型等.编写没有内存泄漏的程序,对提高程序稳定性,提高用户体验具有重要的意义.因此,学习Java利用java编写程序的时候,要特别注意内存泄漏相关的问题.虽然JVM提供了自动垃圾回收机制,但是还是有很多情况会导致内存泄漏. 内存泄漏主要原因就是一个生命周期长的对象,持有了一个生命周期短的对象的引用.这样,会导致短的对象在该回收时候无法被回收.Android中比较典型的有:1.静态变量持有Activity的co

  • Android内存泄漏实战解析

    Java是垃圾回收语言的一种,其优点是开发者无需特意管理内存分配,降低了应用由于局部故障(segmentation fault)导致崩溃,同时防止未释放的内存把堆栈(heap)挤爆的可能,所以写出来的代码更为安全. 不幸的是,在Java中仍存在很多容易导致内存泄漏的逻辑可能(logical leak).如果不小心,你的Android应用很容易浪费掉未释放的内存,最终导致内存用光的错误抛出(out-of-memory,OOM). 1.一般内存泄漏(traditional memory leak)的

随机推荐