Basic Principles:

MlLeaksFinder is a third-party library that detects memory leaks while the app is running and can help find problems during code debugging. The method swizzled hook object lifecycle method sends a message to the object at the end of its lifecycle after the specified time. If the object has been freed at this point, the message will not be executed, but if it has not been freed, a memory leak has occurred and the message will be executed to alert the developer. Recursively, the position of a view or controller’s tree node is recorded, which helps to locate which object is not released. MLLeaksFinder has introduced the FBRetainCycleDetector, which can check for circular references.

There are two types of memory leaks. The first type is when an object is not referenced and is not released in memory. The second is when the object is referenced in a loop and cannot be freed. In the RAC scenario, passing is a memory leak caused by 2.

Source code analysis:

Mleaksfinder.h defines the macros used in MLeaksFinder

MLeaksFinder.h

#ifdef MEMORY_LEAKS_FINDER_ENABLED //_INTERNAL_MLF_ENABLED macro controls when the MLLeaksFinder library starts detection. You can customize this timing. By default, it starts in DEBUG mode. RELEASE mode does not start // it is pre-compiled #define _INTERNAL_MLF_ENABLED MEMORY_LEAKS_FINDER_ENABLED #else #define _INTERNAL_MLF_ENABLED DEBUG #endif //_INTERNAL_MLF_RC_ENABLED macro is used to control whether to enable cyclic reference detection # IFDEF MEMORY_LEAKS_FINDER_RETAIN_CYCLE_ENABLED #define _INTERNAL_MLF_RC_ENABLED MEMORY_LEAKS_FINDER_RETAIN_CYCLE_ENABLED Since MLLeaksFinder references a third library to check for circular references, you must be using COCOAP //ODS in your current project to use this feature. #elif COCOAPODS #define _INTERNAL_MLF_RC_ENABLED COCOAPODS #endifCopy the code

MLLeakeObjectProxy is used to check for circular references to leak objects

MLeakedObjectProxy

// To check whether the current leak object has been added to the leak object collection, if so, + (BOOL)isAnyObjectLeakedAtPtrs:(NSSet *) PTRS {NSAssert([NSThread isMainThread], @"Must be in main thread."); static dispatch_once_t onceToken; Dispatch_once (&onceToken, ^{leakedObjectPtrs = [[NSMutableSet alloc] init]; dispatch_once(&onceToken, ^{leakedObjectPtrs = [NSMutableSet alloc] init]; }); if (! ptrs.count) { return NO; } //NSSet intersects if ([leakedObjectPtrs intersectsSet: PTRS]) {return YES; } else { return NO; } } + (void)addLeakedObject:(id)object { NSAssert([NSThread isMainThread], @"Must be in main thread."); MLeakedObjectProxy *proxy = [[MLeakedObjectProxy alloc] init]; MLeakedObjectProxy = [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; 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 }Copy the code

The main functions of NSObject+MemoryLeak are to store the tree structure of the parent nodes of an object, method Swizzle logic, whitelist, and determine whether an object has memory leaks

NSObject+MemoryLeak

- (BOOL)willDealloc { NSString *className = NSStringFromClass([self class]); If ([[NSObject classNamesInWhiteList] containsObject:className]) return NO; 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; // If the object has been freed, strongSelf nil calls this method and nothing happens [strongSelf assertNotDealloc]; }); return YES; - (void)assertNotDealloc {// Check if it has been recorded, if so, 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]); } // By recursively recording the position of each node in the tree structure, - (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 {// by precompiling control hook method #if _INTERNAL_MLF_ENABLED # if_internal_MLF_rc_enabled // Just find a place to set up FBRetainCycleDetector 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 is used to add a method to a class, OriginalSEL is the method name,method_getIm //plementtation is the method implementation, // There is no originalSEL method in the current class (not in interface, but in implementaion), SwizzledMethod (swizzledMethod) returns true Otherwise return false class_addMethod(class, originalSEL, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); If (didAddMethod) {//didAddMethod is true, swizzledMethod does not exist before, class_addMethod adds a name called origninalSEL, The implementation is the swizzledMoethod function. class_replaceMethod(class, swizzledSEL, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else {//didAddMethod is false, swizzledMethod already exists, Method_exchangeImplementations (originalMethod, swizzledMethod); } #endif }Copy the code

UINavigationController + MemoryLeak mainly uses the method of UINavigationController to detect the life cycle of sub-UIViewController pages. The lifecycle of UIViewController is determined by the methods of UINavigationController and some methods of UIViewController itself.

UINavigationController + MemoryLeak

Load (dspatch_once) is initialized only once. Load (dspatch_once) is initialized only once. + (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 (self.splitViewController) {// After the next root page push, Id detailViewController = objc_getAssociatedObject(self, kPoppedDetailVCKey); If ([detailViewController isKindOfClass:[UIViewController Class]]) {// Reclaim the previously pop root page [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; } / / if the current page is spliteViewController root page (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]; // When multiple pages are popped at once, viewDidDisappear is probably not called. For (UIViewController *viewController in poppedViewControllers) {[viewController willDealloc]; } return poppedViewControllers; }Copy the code

UIViewController + MemoryLeak

- (void)swizzled_viewDidDisappear:(BOOL)animated { [self swizzled_viewDidDisappear:animated]; ViewDidDisappear if ([objc_getAssociatedObject(self, objc_getAssociatedObject(self, kHasBeenPoppedKey) boolValue]) { [self willDealloc]; } } - (void)swizzled_dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion { [self swizzled_dismissViewControllerAnimated:flag completion:completion]; / / dismiss presentedViewController, Release it (but was released when the viewController?) UIViewController * dismissedViewController = self presentedViewController; if (! DismissedViewController && self. PresentingViewController) {/ / release their dismissedViewController = self; } if (! dismissedViewController) return; [dismissedViewController willDealloc]; // present viewcontroller willDealloc without DidDisappear; }Copy the code