您的位置:首页技术文章
文章详情页

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

【字号: 日期:2022-09-16 16:21:09浏览:3作者:猪猪

对于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内存泄漏的资料请关注好吧啦网其它相关文章!

标签: IOS
相关文章: