APM 您所在的位置:网站首页 内存测试内存锁定失败 APM

APM

2023-05-31 13:39| 来源: 网络整理| 查看: 265

简介

MLeaksFinder是微信开源的可以在iOS开发阶段用来检测内存泄漏的库,可以自动的在UIView和UIViewController中发现泄漏,通过弹出框显示在View-ViewController中的泄漏对象。除了发现UIView和UIViewController的泄漏对象,开发者也可以扩展到其他类型的对象。

并且可以通过FBRetainCycleDetector找到引用环。

内存分类 Leaked memory 不被应用引用的内存,无法再次使用或者释放(可以使用Leaks Instrument工具检测到) Abandoned memory 没有用处的,仍被应用引用的内存 Cached memory 仍然被应用引用,可以被再次使用从而得到更好的性能 原理

除了单例及其持有它的强引用,当一个UIViewController被pop或dismiss后,对应的View,View的subViews等对象将很快被释放。

Hook

Hook所有页面退出的情况作为检测时机

UIViewController

-dismiss -viewDidDisappear

UINavigationController

-pop -popToRoot -popToViewController

视图层级

页面生命周期 UINavigationController -push/pop -[MyUINavigationController pushViewController:animated:] -[ParentViewController viewWillDisappear:] -[ChildViewController viewWillAppear:] -[ParentViewController viewDidDisappear:] -[ChildViewController viewDidAppear:] -[MyUINavigationController popViewControllerAnimated:] -[ChildViewController viewWillDisappear:] -[ParentViewController viewWillAppear:] -[ChildViewController viewDidDisappear:] -[ParentViewController viewDidAppear:] -[ChildViewController dealloc]

从上述的页面生命周期可以看到,viewDidDisappear在页面做push的时候也会触发,所以不能单独以viewDidDisappear作为检测时机,需要以先有popViewControllerAnimated,后有viewDidDisappear为特征,才能作为页面出栈的判断。

Left-edge swipe

UINavigationController -pop有一种特殊情况是左滑手势

先左滑-后松开

-[MyUINavigationController popViewControllerAnimated:] -[ChildViewController viewWillDisappear:] -[ParentViewController viewWillAppear:] -[ParentViewController viewWillDisappear:] -[ParentViewController viewDidDisappear:] -[ChildViewController viewWillAppear:] -[ChildViewController viewDidAppear:]

先左滑-后完成

-[MyUINavigationController popViewControllerAnimated:] -[ChildViewController viewWillDisappear:] -[ParentViewController viewWillAppear:] -[ChildViewController viewDidDisappear:] -[ParentViewController viewDidAppear:] -[ChildViewController dealloc]

由于左滑开始的时候,popViewControllerAnimated已经触发,而且时长由用户决定。由于检测需要使用延迟执行,查看页面等对象是否释放,所以不能单独以

popViewControllerAnimated为检测时机,需要以先有popViewControllerAnimated,后有viewDidDisappear为特征,才能作为页面出栈的判断。

popToRoot/popToViewController -[MyUINavigationController pushViewController:animated:] -[MyUINavigationController pushViewController:animated:] -[ParentViewController viewWillDisappear:] -[OtherViewController viewWillAppear:] -[ParentViewController viewDidDisappear:] -[OtherViewController viewDidAppear:] -[OtherViewController viewWillDisappear:] -[ChildViewController viewWillAppear:] -[OtherViewController viewDidDisappear:] -[ChildViewController viewDidAppear:] -[MyUINavigationController popToRootViewControllerAnimated:] -[ChildViewController viewWillDisappear:] -[ParentViewController viewWillAppear:] -[OtherViewController dealloc] -[ChildViewController viewDidDisappear:] -[ParentViewController viewDidAppear:] -[ChildViewController dealloc]

从页面生命周期可以看到,除了TopViewController和RootViewController/ToViewController,其他处于中间的ViewController在弹出页面栈的时候不会有生命周期的调用,可以获取中间的ViewController开始检测。

UIViewController -present/dismiss Modal

Modal是指模态,模式状态,模态是人机交互过程中的一种状态,表现为用户相同的操作下可以产生不同的结果。例如使打开文件夹时,点击和右击会触发不同的效果。弹框也有对应的对话框,弹出框,全屏框等效果。

根据Modal的不同,ParentViewController的Appear状态会不同,当ParentViewController完全被遮挡的时候,会有Appear相关的页面生命周期变化。

不同模态下的页面生命周期不同,可以分为3类。

半覆盖 UIModalPresentationPageSheet UIModalPresentationFormSheet UIModalPresentationAutomatic UIModalPresentationCustom UIModalPresentationPopover -[ParentViewController presentViewController:animated:completion:] -[ChildViewController viewWillAppear:] -[ChildViewController viewDidAppear:] -[ChildViewController dismissViewControllerAnimated:completion:] -[ChildViewController viewWillDisappear:] -[ChildViewController viewDidDisappear:] -[ChildViewController dealloc] 全覆盖 UIModalPresentationCurrentContext UIModalPresentationFullScreen -[ParentViewController presentViewController:animated:completion:] -[ParentViewController viewWillDisappear:] -[ChildViewController viewWillAppear:] -[ChildViewController viewDidAppear:] -[ParentViewController viewDidDisappear:] -[ChildViewController dismissViewControllerAnimated:completion:] -[ChildViewController viewWillDisappear:] -[ParentViewController viewWillAppear:] -[ParentViewController viewDidAppear:] -[ChildViewController viewDidDisappear:] -[ChildViewController dealloc] 报错 UIModalPresentationOverFullScreen UIModalPresentationNone *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'The specified modal presentation style doesn't have a corresponding presentation controller.'

这两种模态,会导致报OC语言层面异常,模态展示方式和对应controller不匹配

代码解析

使用了Method Swizzle对页面的生命周期进行了Hook

Method Swizzle

NSObject (MemoryLeak)中对swizzle做了封装

+ (void)swizzleSEL:(SEL)originalSEL withSEL:(SEL)swizzledSEL { #if _INTERNAL_MLF_ENABLED 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); } #endif }

使用宏作为功能开关,DEBUG的状态下打开,或者手动设置MEMORY_LEAKS_FINDER_ENABLED为1打开

//#define MEMORY_LEAKS_FINDER_ENABLED 0 #ifdef MEMORY_LEAKS_FINDER_ENABLED #define _INTERNAL_MLF_ENABLED MEMORY_LEAKS_FINDER_ENABLED #else #define _INTERNAL_MLF_ENABLED DEBUG #endif UINavigationController (MemoryLeak) popViewControllerAnimated

在该生命周期中标记kHasBeenPoppedKey为YES

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

在UIViewController的viewWillAppear中,标记kHasBeenPoppedKey为NO

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

获取中间的ViewController开始检测

- (NSArray *)swizzled_popToViewController:(UIViewController *)viewController animated:(BOOL)animated { NSArray *poppedViewControllers = [self swizzled_popToViewController:viewController animated:animated]; for (UIViewController *viewController in poppedViewControllers) { [viewController willDealloc]; } return poppedViewControllers; } popToRootViewControllerAnimated

获取中间的ViewController开始检测

- (NSArray *)swizzled_popToRootViewControllerAnimated:(BOOL)animated { NSArray *poppedViewControllers = [self swizzled_popToRootViewControllerAnimated:animated]; for (UIViewController *viewController in poppedViewControllers) { [viewController willDealloc]; } return poppedViewControllers; } UIViewController (MemoryLeak) viewDidDisappear

判断kHasBeenPoppedKey为YES,然后开始检测

- (void)swizzled_viewDidDisappear:(BOOL)animated { [self swizzled_viewDidDisappear:animated]; if ([objc_getAssociatedObject(self, kHasBeenPoppedKey) boolValue]) { [self willDealloc]; } } dismissViewControllerAnimated - (void)swizzled_dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion { [self swizzled_dismissViewControllerAnimated:flag completion:completion]; UIViewController *dismissedViewController = self.presentedViewController; if (!dismissedViewController && self.presentingViewController) { dismissedViewController = self; } if (!dismissedViewController) return; [dismissedViewController willDealloc]; }

对应的presentedViewController,presentingViewController,self对应如下,获取到dismissedViewController并执行检测。由于dismissedViewController并没有边缘手势的影响,不需要使用dimiss和viewDidDisappear叠加的方式来判断检测的时机。

-[ChildViewController dismissViewControllerAnimated:completion:] self.presentedViewController (null) self.presentingViewController self -[ChildViewController viewWillDisappear:] -[ChildViewController viewDidDisappear:] -[ChildViewController dealloc] Action

获取对应类的weakSelf,延迟2秒在主队列执行,调用weakSelf中的assertNotDealloc方法,方法还能响应的话就记录相关信息

类结构

NSObject

UIViewController

UINavigationController UIPageViewController UISplitViewController UITabBarController

UIView

检测对象中存在继承关系,检测方法也使用了继承关系

代码解析 NSObject (MemoryLeak) willDealloc 如果在白名单中,不进行检测 如果泄漏对象是最近一次UIControl事件的触发者,不进行检测(按照修改记录来看,当使用Button来pop ViewController的时候,VC不能及时释放) - (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; }

UIApplication (MemoryLeak)

获取和更新LatestSender - (BOOL)swizzled_sendAction:(SEL)action to:(id)target from:(id)sender forEvent:(UIEvent *)event { objc_setAssociatedObject(self, kLatestSenderKey, @((uintptr_t)sender), OBJC_ASSOCIATION_RETAIN); return [self swizzled_sendAction:action to:target from:sender forEvent:event]; }

UITouch (MemoryLeak)

获取和更新LatestSender - (void)swizzled_setView:(UIView *)view { [self swizzled_setView:view]; if (view) { objc_setAssociatedObject([UIApplication sharedApplication], kLatestSenderKey, @((uintptr_t)view), OBJC_ASSOCIATION_RETAIN); } } 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]); } willReleaseChildren 遍历视图树,构建视图堆栈ViewStack 记录父节点 - (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]; } } UINavigationController (MemoryLeak) willDealloc 检测self.viewControllers - (BOOL)willDealloc { if (![super willDealloc]) { return NO; } [self willReleaseChildren:self.viewControllers]; return YES; } UIPageViewController (MemoryLeak) willDealloc 检测self.viewControllers - (BOOL)willDealloc { if (![super willDealloc]) { return NO; } [self willReleaseChildren:self.viewControllers]; return YES; } UISplitViewController (MemoryLeak) willDealloc 检测self.viewControllers - (BOOL)willDealloc { if (![super willDealloc]) { return NO; } [self willReleaseChildren:self.viewControllers]; return YES; } UITabBarController (MemoryLeak) willDealloc 检测self.viewControllers - (BOOL)willDealloc { if (![super willDealloc]) { return NO; } [self willReleaseChildren:self.viewControllers]; return YES; } UIViewController (MemoryLeak) willDealloc 检测self.childViewControllers 检测self.presentedViewController viewDidLoad的情况下从self.view开始遍历subviews - (BOOL)willDealloc { if (![super willDealloc]) { return NO; } [self willReleaseChildren:self.childViewControllers]; [self willReleaseChild:self.presentedViewController]; if (self.isViewLoaded) { [self willReleaseChild:self.view]; } return YES; } UIView (MemoryLeak) willDealloc 遍历view的subviews UIView (MemoryLeak) - (BOOL)willDealloc { if (![super willDealloc]) { return NO; } [self willReleaseChildren:self.subviews]; return YES; } 引用

MLeaksFinder Github

MLeaksFinder 新特性

MLeaksFinder:精准 iOS 内存泄漏检测工具



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有