一、基本原理:
MLeaksFinder是app运行的过程中,检测内存泄漏的第三方库,可以帮助在代码调试阶段发现问题。通过method swizzled hook 对象生命周期的方法,在对象结束生命周期的时候,在指定时间之后给对象发送某个消息。如果这个时候对象已经被释放,消息不会被执行,如果没有释放说明发生了内存泄漏,消息就会被执行,从而提醒开发人员。通过递归的方式,会记录下某个视图或者controller的树形节点的位置,能更好的帮助定位到具体哪个对象没有被释放。MLeaksFinder引入了FBRetainCycleDetector,可以检查循环引用。
内存泄漏分为2种,第1种是对象没有被任何引用,在内存中没有被释放。第2种是对象发生循环引用,无法被释放。在RAC的场景下,通过是2引起的内存泄漏。
二、源码分析:
1、MLeaksFinder.h
MLeaksFinder.h定义了MLeaksFinder中使用的宏
#ifdef MEMORY_LEAKS_FINDER_ENABLED
//_INTERNAL_MLF_ENABLED 宏用来控制 MLLeaksFinder库
//什么时候开启检测,可以自定义这个时机,默认则是在DEBUG模式下会启动,RELEASE模式下不启动
//它是通过预编译来实现的
#define _INTERNAL_MLF_ENABLED MEMORY_LEAKS_FINDER_ENABLED
#else
#define _INTERNAL_MLF_ENABLED DEBUG
#endif
//_INTERNAL_MLF_RC_ENABLED 宏用来控制 是否开启循环引用的检测
#ifdef MEMORY_LEAKS_FINDER_RETAIN_CYCLE_ENABLED
#define _INTERNAL_MLF_RC_ENABLED MEMORY_LEAKS_FINDER_RETAIN_CYCLE_ENABLED
//COCOAPODS 因为MLLeaksFinder引用了第三库用来检查循环引用,所以必须是当前项目中使用了COCOAP
//ODS,才能使用这个功能。
#elif COCOAPODS
#define _INTERNAL_MLF_RC_ENABLED COCOAPODS
#endif
2、MLLeakeObjectProxy
MLeakedObjectProxy用来对泄漏对象检查循环引用
//用来检查当前泄漏对象是否已经添加到泄漏对象集合中,如果是,就不再添加也不再提示开发者
+ (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;
}
//NSSet求交集
if ([leakedObjectPtrs intersectsSet:ptrs]) {
return YES;
} else {
return NO;
}
}
+ (void)addLeakedObject:(id)object {
NSAssert([NSThread isMainThread], @"Must be in main thread.");
//创建用于检查循环引用的objectProxy对象
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
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)
buttonIndex {
#if _INTERNAL_MLF_RC_ENABLED
dispatch_async(dispatch_get_global_queue(0, 0), ^{
FBRetainCycleDetector *detector = [FBRetainCycleDetector new];
[detector addCandidate:self.object];
NSSet *retainCycles = [detector findRetainCyclesWithMaxCycleLength:20];
BOOL hasFound = NO;
//retainCycles中是找到的所有循环引用的链
for (NSArray *retainCycle in retainCycles) {
NSInteger index = 0;
for (FBObjectiveCGraphElement *element in retainCycle) {
//找到当前内存泄漏对象所在的循环引用的链
if (element.object == object) {
//把当前对象调整到第一个的位置,方便查看
NSArray *shiftedRetainCycle = [self shiftArray:retainCycle
toIndex:index];
dispatch_async(dispatch_get_main_queue(), ^{
[MLeaksMessenger alertWithTitle:@"Retain Cycle"
message:[NSString
stringWithFormat:@"%@",
shiftedRetainCycle]];
});
hasFound = YES;
break;
}
++index;
}
if (hasFound) {
break;
}
}
if (!hasFound) {
dispatch_async(dispatch_get_main_queue(), ^{
[MLeaksMessenger alertWithTitle:@"Retain Cycle"
message:@"Fail to find a retain cycle"];
});
}
});
#endif
}
3、NSObject+MemoryLeak
NSObject+MemoryLeak主要功能存储对象的父子节点的树形结构,method swizzle逻辑 ,白名单以及判断对象是否发生内存泄漏
- (BOOL)willDealloc {
NSString *className = NSStringFromClass([self class]);
//通过白名单可以配置哪些对象不纳入检查,例如一些单例
if ([[NSObject classNamesInWhiteList] 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为nil 调用该方法什么也不发生
[strongSelf assertNotDealloc];
});
return YES;
}
//改方法被调用说明改对象已经发生内存泄漏
- (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]);
}
//主要通过递归来记录每个节点在树形结构中的位置,以及父子节点的指针
- (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];
}
}
+ (void)swizzleSEL:(SEL)originalSEL withSEL:(SEL)swizzledSEL {
//通过预编译控制是否hook方法
#if _INTERNAL_MLF_ENABLED
//通过预编译控制是否检查循环引用
#if _INTERNAL_MLF_RC_ENABLED
// Just find a place to set up FBRetainCycleDetector.
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dispatch_async(dispatch_get_main_queue(), ^{
[FBAssociationManager hook];
});
});
#endif
Class class = [self class];
Method originalMethod = class_getInstanceMethod(class, originalSEL);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSEL);
BOOL didAddMethod =
//class_addMethod主要是用来给某个类添加一个方法,originalSEL相当于是方法名称,method_getIm
//plementtation是方法实现, 它返回一个BOOL类型的值
//在当前class中没有叫originalSEL的方法(
//具体不是看interface里没有没有声明,而是看implementaion文件里有没有方法实现),
// 并且有swizzledMethod方法的实现
//这个时候该函数会返回true,其他情况均返回false
class_addMethod(class,
originalSEL,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
//didAddMethod为true 说明swizzledMethod之前不存在,通过class_addMethod函数添加了一个名字叫origninalSEL,实现是swizzledMoethod函数。
class_replaceMethod(class,
swizzledSEL,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
//didAddMethod为false 说明swizzledMethod方法已经存在,直接交换二者实现
method_exchangeImplementations(originalMethod, swizzledMethod);
}
#endif
}
4、UINavigationController + MemoryLeak
UINavigationController + MemoryLeak 主要是通过UINavigationController的方法去检测子UIViewController页面的生命周期,UIViewController的生命周期由UINavigationController的方法和UIViewController自身的一些方法共同决定的。
//现在在具体的类型中添加方法hook,加载load中并且调用dspatch_once来保证只初始化一次,load是必然会调用的,并且category的load方法调用和类自身的load方法调用是分开的,互不干扰。
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleSEL:@selector(pushViewController:animated:) withSEL:@
selector(swizzled_pushViewController:animated:)];
[self swizzleSEL:@selector(popViewControllerAnimated:) withSEL:@
selector(swizzled_popViewControllerAnimated:)];
[self swizzleSEL:@selector(popToViewController:animated:) withSEL:@
selector(swizzled_popToViewController:animated:)];
[self swizzleSEL:@selector(popToRootViewControllerAnimated:) withSEL:@
selector(swizzled_popToRootViewControllerAnimated:)];
});
}
- (void)swizzled_pushViewController:(UIViewController *)viewController
animated:(BOOL)animated {
if (self.splitViewController) {
//这里主要是考虑到app中有使用splitViewController的情况的时候,下一个根页面push之后,
//之前被pop的根页面才会回收
id detailViewController = objc_getAssociatedObject(self,
kPoppedDetailVCKey);
if ([detailViewController isKindOfClass:[UIViewController class]]) {
//回收之前被pop的根页面
[detailViewController willDealloc];
objc_setAssociatedObject(self, kPoppedDetailVCKey, nil,
OBJC_ASSOCIATION_RETAIN);
}
}
[self swizzled_pushViewController:viewController animated:animated];
}
- (UIViewController *)swizzled_popViewControllerAnimated:(BOOL)animated {
UIViewController *poppedViewController = [self swizzled_popViewControllerAnimated:animated];
if (!poppedViewController) {
return nil;
}
//当前页面是spliteViewController根页面
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;
}
- (NSArray<UIViewController *> *)swizzled_popToViewController:(
UIViewController *)viewController animated:(BOOL)animated {
NSArray<UIViewController *> *poppedViewControllers = [self
swizzled_popToViewController:viewController animated:animated];
//一次性pop多个页面的时候,这些页面的viewDidDisappear估计都没有被调用,直接回收了
for (UIViewController *viewController in poppedViewControllers) {
[viewController willDealloc];
}
return poppedViewControllers;
}
5、UIViewController + MemoryLeak
- (void)swizzled_viewDidDisappear:(BOOL)animated {
[self swizzled_viewDidDisappear:animated];
//仅仅当是pop引起viewDidDisappear的时候才释放(当被挡住之后也会调用ViewDidDisappear)
if ([objc_getAssociatedObject(self, kHasBeenPoppedKey) boolValue]) {
[self willDealloc];
}
}
- (void)swizzled_dismissViewControllerAnimated:(BOOL)flag
completion:(void (^)(void))completion {
[self swizzled_dismissViewControllerAnimated:flag completion:completion];
//dismiss掉presentedViewController,释放它 (但是什么时候当前viewController被释放呢)
UIViewController *dismissedViewController = self.presentedViewController;
if (!dismissedViewController && self.presentingViewController) {
//释放自己
dismissedViewController = self;
}
if (!dismissedViewController) return;
//以present出来的viewcontroller,不通过DidDisappear去判断是否释放了
[dismissedViewController willDealloc];
}
网友评论