MLeakFinder

    xiaoxiao2026-02-28  6

    MLeaksFinder 内存泄漏检测工具

    介绍

    MLeaksFinder 提供了内存泄露检测更好的解决方案。只需要引入 MLeaksFinder,就可以自动在 App 运行过程检测到内存泄露的对象并立即提醒,无需打开额外的工具,也无需为了检测内存泄露而一个个场景去重复地操作。MLeaksFinder 目前能自动检测 UIViewController 和 UIView 对象的内存泄露,而且也可以扩展以检测其它类型的对象。 MLeaksFinder 的使用很简单,参照 https://github.com/Zepo/MLeaksFinder ,基本上就是把 MLeaksFinder 目录下的文件添加到你的项目中,就可以在运行时(debug 模式下)帮助你检测项目里的内存泄露了,无需修改任何业务逻辑代码,而且只在 debug 下开启,完全不影响你的 release 包。

    当发生内存泄露时,MLeaksFinder 会中断言,并准确的告诉你哪个对象泄露了。这里设计为中断言而不是打日志让程序继续跑,是因为很多人不会去看日志,断言则能强制开发者注意到并去修改,而不是犯拖延症。

    原理

    从 MLeaksFinder 的使用方法可以看出,MLeaksFinder 具备以下优点:

    使用简单,不侵入业务逻辑代码,不用打开 Instrument 不需要额外的操作,你只需开发你的业务逻辑,在你运行调试时就能帮你检测 内存泄露发现及时,更改完代码后一运行即能发现(这点很重要,你马上就能意识到哪里写错了) 精准,能准确地告诉你哪个对象没被释放 原理

    MLeaksFinder 一开始从 UIViewController 入手。我们知道,当一个 UIViewController 被 pop 或 dismiss 后,该 UIViewController 包括它的 view,view 的 subviews 等等将很快被释放(除非你把它设计成单例,或者持有它的强引用,但一般很少这样做)。于是,我们只需在一个 ViewController 被 pop 或 dismiss 一小段时间后,看看该 UIViewController,它的 view,view 的 subviews 等等是否还存在。

    具体的方法是,为基类 NSObject 添加一个方法 -willDealloc 方法,该方法的作用是,先用一个弱指针指向 self,并在一小段时间(3秒)后,通过这个弱指针调用 -assertNotDealloc ,而 -assertNotDealloc 主要作用是直接中断言.

    - (BOOL)willDealloc { __weak id weakSelf = self; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [weakSelf assertNotDealloc]; }); return YES; } - (void)assertNotDealloc { NSAssert(NO, @“”); }

    这样,当我们认为某个对象应该要被释放了,在释放前调用这个方法,如果3秒后它被释放成功,weakSelf 就指向 nil,不会调用到 -assertNotDealloc 方法,也就不会中断言,如果它没被释放(泄露了), -assertNotDealloc 就会被调用中断言。这样,当一个 UIViewController 被 pop 或 dismiss 时(我们认为它应该要被释放了),我们遍历该 UIViewController 上的所有 view,依次调 -willDealloc ,若3秒后没被释放,就会中断言。

    在这里,有几个问题需要解决:

    1、不入侵开发代码

    这里使用了 AOP 技术,hook 掉 UIViewController 和 UINavigationController 的 pop 跟 dismiss 方法,关于如何 hook,请参考 Method Swizzling 。

    + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [self swizzleSEL:@selector(viewDidDisappear:) withSEL:@selector(swizzled_viewDidDisappear:)]; [self swizzleSEL:@selector(viewWillAppear:) withSEL:@selector(swizzled_viewWillAppear:)]; [self swizzleSEL:@selector(dismissViewControllerAnimated:completion:) withSEL:@selector(swizzled_dismissViewControllerAnimated:completion:)]; }); } + (void)swizzleSEL:(SEL)originalSEL withSEL:(SEL)swizzledSEL { Class class = [self class]; Method originalMethod = class_getInstanceMethod(class, originalSEL); Method swizzledMethod = class_getInstanceMethod(class, swizzledSEL); BOOL didAddMethod = class_addMethod(class, originalSEL, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(class, swizzledSEL, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } }

    2、遍历相关对象

    在实际项目中,我们发现有时候一个 UIViewController 被释放了,但它的 view 没被释放,或者一个 UIView 被释放了,但它的某个 subview 没被释放。这种内存泄露的情况很常见,因此,我们有必要遍历基于 UIViewController 的整棵 View-ViewController 树。我们通过 UIViewController 的 presentedViewController 和 view 属性,UIView 的 subviews 属性等递归遍历。对于某些 ViewController,如 UINavigationController,UISplitViewController 等,我们还需要遍历 viewControllers 属性。

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

    3、构建堆栈信息

    需要构建 View-ViewController stack 信息以告诉开发者是哪个对象没被释放。在递归遍历 View-ViewController 树时,子节点的 stack 信息由父节点的 stack 信息加上子结点信息即可。

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

    4、例外机制

    对于有些 ViewController,在被 pop 或 dismiss 后,不会被释放(比如单例),因此需要提供机制让开发者指定哪个对象不会被释放,这里可以通过重载上面的 -willDealloc 方法,直接 return NO 即可。

    5、特殊情况

    对于某些特殊情况,释放的时机不大一样(比如系统手势返回时,在划到一半时 hold 住,虽然已被 pop,但这时还不会被释放,ViewController 要等到完全 disappear 后才释放),需要做特殊处理,具体的特殊处理视具体情况而定。

    6、系统View

    某些系统的私有 View,不会被释放(可能是系统 bug 或者是系统出于某些原因故意这样做的,这里就不去深究了),因此需要建立白名单

    7、手动扩展

    MLeaksFinder目前只检测 ViewController 跟 View 对象。为此,MLeaksFinder 提供了一个手动扩展的机制,你可以从 UIViewController 跟 UIView 出发,去检测其它类型的对象的内存泄露。如下所示,我们可以检测 UIViewController 底下的 View Model:

    - (BOOL)willDealloc { if (![super willDealloc]) { return NO; } MLCheck(self.viewModel); return YES; }

    这里的原理跟上面的是一样的,宏 MLCheck() 做的事就是为传进来的对象建立 View-ViewController stack 信息,并对传进来的对象调用 -willDealloc 方法。

    参考资料1 Github/MLeaksFinder 运行时

    转载请注明原文地址: https://ju.6miu.com/read-1307458.html
    最新回复(0)