MLeaksFinder是 iOS 平台的自动内存泄漏检测工具,下面以demo来实现检测视图控制器是否内存泄漏,实现类似的功能,简单地了解MLeaksFinder的原理。
总体思路:在视图控制器弹出栈 && 视图完全消失时,监听对象是否已被正常销毁
1.新建NSObject分类,提供简便的方法交换类方法:
#import "NSObject+JXRuntime.h"
#import<objc/runtime.h>
@implementation NSObject (JXRuntime)
+(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));
//若类没有实现需要被替换的原方法,也就是上一步添加原方法成功,此时已将本来要swizzle的方法的实现直接复制进原方法里。
//还需要把新方法IMP指向原方法的实现:
if (didAddMethod) {
class_replaceMethod(class, swizzledSEL, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}
//类有对应的原方法,则正常交换
else{
method_exchangeImplementations(originalMethod, swizzledMethod);
}
//参考:https://www.jianshu.com/p/1bacd182329f
}
2.新建UIViewController分类,交换viewWillAppear:和viewDidDisappear:方法
#import "UIViewController+Leaks.h"
#import "NSObject+JXRuntime.h"
#import <objc/runtime.h>
const void *const kHasBeenPoppedKey = "kHasBeenPoppedKey";//标记是否已经出栈
@implementation UIViewController (Leaks)
+ (void)load{
//正常情况load只会执行一次,使用dispatch_once是为了防止人为调用load方法
//在load方法而不是在initalize方法中进行交换的原因:(1)load在类被加载时调用,initalize在类或其子类收到第一条消息时才被调用,类似懒加载。(2)load方法在父类、子类、分类中是独立的
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleSEL:@selector(viewWillAppear:) withSEL:@selector(swizzled_viewWillAppear:)];
[self swizzleSEL:@selector(viewDidDisappear:) withSEL:@selector(swizzled_viewDidDisappear:)];
});
}
- (void) swizzled_viewWillAppear:(BOOL) animated{
[self swizzled_viewWillAppear:animated];
//标记为进栈
objc_setAssociatedObject(self, kHasBeenPoppedKey, @(NO), OBJC_ASSOCIATION_RETAIN);
}
-(void) swizzled_viewDidDisappear:(BOOL) animated{
[self swizzled_viewDidDisappear:animated];
//已经出栈
if ([objc_getAssociatedObject(self, kHasBeenPoppedKey) boolValue] == YES) {
//判断对象是否被销毁
[self willDealloc];
}
}
@end
3.新建UINavigationController分类,交换popViewControllerAnimated:方法。
#import "UINavigationController+Leaks.h"
#import <objc/runtime.h>
#import "NSObject+JXRuntime.h"
@implementation UINavigationController (Leaks)
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleSEL:@selector(popViewControllerAnimated:) withSEL:@selector(swizzled_popViewControllerAnimated:)];
});
}
- (UIViewController*)swizzled_popViewControllerAnimated:(BOOL)animated{
UIViewController *poppedViewController = [self swizzled_popViewControllerAnimated:animated];
extern const void * const kHasBeenPoppedKey;
//标记为已经出栈
objc_setAssociatedObject(poppedViewController, kHasBeenPoppedKey, @(YES), OBJC_ASSOCIATION_RETAIN);
return poppedViewController;
}
@end
4.新建UINavigationController分类,交换popViewControllerAnimated:方法。
#import "UINavigationController+Leaks.h"
#import <objc/runtime.h>
#import "NSObject+JXRuntime.h"
@implementation UINavigationController (Leaks)
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleSEL:@selector(popViewControllerAnimated:) withSEL:@selector(swizzled_popViewControllerAnimated:)];
});
}
- (UIViewController*)swizzled_popViewControllerAnimated:(BOOL)animated{
UIViewController *poppedViewController = [self swizzled_popViewControllerAnimated:animated];
extern const void * const kHasBeenPoppedKey;
//标记为已经出栈
objc_setAssociatedObject(poppedViewController, kHasBeenPoppedKey, @(YES), OBJC_ASSOCIATION_RETAIN);
return poppedViewController;
}
@end