返回顶部
首页 > 资讯 > 移动开发 >IOS内存泄漏检查方法及重写MLeakFinder
  • 176
分享到

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

IOS内存泄漏MLeakFinder 2022-06-05 11:06:41 176人浏览 泡泡鱼
摘要

目录MLeakFinder-原理1,首先看viewWillAppear2,在看viewDidAppear3,我们看什么时候会被标记为YES呢?4,我们重点看willDealloc5,

对于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;
}

我们可以看到,会先判断当前的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];
}

第二步:判断该对象是否是上一次发送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内存泄漏的资料请关注编程网其它相关文章!

--结束END--

本文标题: IOS内存泄漏检查方法及重写MLeakFinder

本文链接: https://lsjlt.com/news/30464.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • IOS内存泄漏检查方法及重写MLeakFinder
    目录MLeakFinder-原理1,首先看viewWillAppear2,在看viewDidAppear3,我们看什么时候会被标记为YES呢?4,我们重点看willDealloc5,...
    99+
    2022-06-05
    IOS 内存泄漏 MLeakFinder
  • nodejs如何检查内存泄漏
    本篇内容介绍了“nodejs如何检查内存泄漏”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成! ...
    99+
    2024-04-02
  • java怎么检查内存泄漏
    本篇内容介绍了“java怎么检查内存泄漏”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!内存泄漏场景长生命周期的对象持有短生命周期对象的引用就...
    99+
    2023-06-30
  • Android中LeakCanary检测内存泄漏的方法
    最近要对产品进行内存泄漏的检查,最后选择了使用Square公司开源的一个检测内存泄漏的函数库LeakCanary,在github上面搜索了一下竟然有1.6w个star,并且Android大神JakeWharton也是这个开源库的贡献者。那么...
    99+
    2023-05-30
    android 内存泄漏 roi
  • android 如何进行内存泄漏检测及解决方法
    内存泄漏是在Android开发中常见的问题之一,它可能导致应用的内存占用逐渐增加,最终影响应用的性能和稳定性。以下是一些常见的方法来进行内存泄漏检测和解决: 1. 使用工具进行内存泄漏检测: Andr...
    99+
    2023-10-03
    android
  • 怎么在java检查内存是否泄漏
    这篇文章给大家介绍怎么在java检查内存是否泄漏,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。Java的特点有哪些Java的特点有哪些1.Java语言作为静态面向对象编程语言的代表,实现了面向对象理论,允许程序员以优雅...
    99+
    2023-06-14
  • Qt下监测内存泄漏的方法
    在写Qt应用程序时,由于是采用C++语言,经常会碰到一个令人棘手的问题,那就是内存泄漏,虽然后面C++为了防止内存泄漏,发布了智能指针以用来避免内存泄漏,但是并不能完全避免。而且智能...
    99+
    2024-04-02
  • Gomap发生内存泄漏解决方法
    目录正文hamp 结构体代码查看占用的内存数量对于 map 内存泄漏的解法正文 Go 程序运行时,有些场景下会导致进程进入某个“高点”,然后就再也下...
    99+
    2022-11-16
    Go map内存泄漏 Go map
  • golang内存泄漏的原因及解决方法是什么
    Golang中的内存泄漏是指程序中分配的内存没有被适时地释放,导致程序占用的内存逐渐增加,最终耗尽系统内存资源。内存泄漏的原因和解决...
    99+
    2023-10-20
    golang
  • android内存泄漏的原因及解决方法是什么
    Android内存泄漏的原因可能有以下几个:1. 长生命周期的对象持有短生命周期的引用:当一个长生命周期的对象持有了一个短生命周期对...
    99+
    2023-08-08
    android
  • C++ 内存泄漏的常见原因及其解决方法
    常见 c++++ 内存泄漏原因:1. 忘记释放指针;2. 双重释放;3. 循环引用;4. 静态变量;5. 全局对象。解决方法:1. 使用智能指针;2. 注意循环引用;3. 避免静态变量;...
    99+
    2024-05-02
    c++ 内存泄漏
  • java中ThreadLocal内存泄漏的解决方法
    小编给大家分享一下java中ThreadLocal内存泄漏的解决方法,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!java基本数据类型有哪些Java的基本数据类型...
    99+
    2023-06-14
  • Android检测Cursor泄漏的原理以及使用方法
    简介: 本文介绍如何在 Android 检测 Cursor 泄漏的原理以及使用方法,还指出几种常见的出错示例。有一些泄漏在代码中难以察觉,但程序长时间运行后必然会出现异常。同时...
    99+
    2022-06-06
    方法 cursor Android
  • 解决 PHP 函数中内存泄漏的方法
    php 函数中的内存泄漏可通过以下方法解决:1. 使用弱引用,防止对象被垃圾回收;2. 使用匿名函数,创建不泄漏引用的对象;3. 使用对象池,重复使用对象,优化内存管理。以上方法可有效防...
    99+
    2024-05-02
    php 内存泄漏
  • windows下C/C++内存泄露检测的方法
    小编给大家分享一下windows下C/C++内存泄露检测的方法,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!检测内存泄露利用Visual Studio调试器和CR...
    99+
    2023-06-15
  • Android性能优化之利用强大的LeakCanary检测内存泄漏及解决办法
    前言: 最近公司C轮融资成功了,移动团队准备扩大一下,需要招聘Android开发工程师,陆陆续续面试了几位Android应聘者,面试过程中聊到性能优化中如何避免内存泄漏问题时...
    99+
    2022-06-06
    leakcanary 内存泄漏 优化 Android
  • Python脚本实现内存泄漏测试的方法及解决过程
    Python脚本实现内存泄漏测试的方法及解决过程,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。我下面这篇文章提供了一种轻巧的内存泄漏测试方法及其python实现,该方法在L...
    99+
    2023-06-05
  • Android 配合Mat工具监听查找内存泄漏的操作方法
    目录1、Android Studio Profiler查看内存2、hprof文件转换3、MAT定位泄漏原因4、回到代码排查原因1、Android Studio Profiler查看内...
    99+
    2024-04-02
  • c语言内存泄漏的解决方法是什么
    在C语言中,内存泄漏是指在动态内存分配后,没有释放该内存导致内存空间无法被再次使用的情况。解决内存泄漏的方法可以包括以下几个方面:1...
    99+
    2023-09-27
    c语言
  • 解决Go语言Websocket应用程序内存泄漏的方法
    解决Go语言Websocket应用程序内存泄漏的方法,需要具体代码示例Websocket是一种在网络上实现全双工通信的协议,常用于实时的数据传输和推送。在Go语言中,我们可以通过使用标准库net/http中的WebSocket模块来编写We...
    99+
    2023-12-14
    websocket Go语言 (Go) 内存泄漏 (Memory Leak)
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作