欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

IOS內(nèi)存泄漏檢查方法及重寫MLeakFinder

 更新時間:2021年04月26日 09:06:20   作者:ZJ_Sandy  
這篇文章主要介紹了IOS內(nèi)存泄漏檢查方法及如何重寫MLeakFinder,幫助ios開發(fā)者維護自身程序,感興趣的朋友可以了解下

對于iOS開發(fā)來講,內(nèi)存泄漏的問題,已經(jīng)是老生常談的話題。在日常的面試中經(jīng)常會提到這些問題。我們?nèi)粘5拈_發(fā)過程中進行內(nèi)存泄漏的檢測,一般是使用instrument工具中的Leaks/Allocation來進行排查,網(wǎng)絡上也有比較高效又好用的內(nèi)存泄漏檢測工具,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進來的時候,添加關聯(lián)對象,并標記為NO

2,在看viewDidAppear

- (void)swizzled_viewDidDisappear:(BOOL)animated {
    [self swizzled_viewDidDisappear:animated];
    if ([objc_getAssociatedObject(self, kHasBeenPoppedKey) boolValue]) {
        [self willDealloc];    
    }
}

通過代碼可以看出,獲取當前關聯(lián)對象的標記,當標記為YES的時候,就會調(diào)用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,即不是內(nèi)存泄漏的。
同時我們查看構建白名單的源碼:使用了一個單例實現(xiàn),確保只有一個,是個私有方法

+ (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. 第二步:判斷該對象是否是上一次發(fā)送action的對象,是的話,不進行內(nèi)存檢測

 //第二步
   
 NSNumber *senderPtr = objc_getAssociatedObject([UIApplication sharedApplication], kLatestSenderKey)  
  
if ([senderPtr isEqualToNumber:@((uintptr_t)self)])
        
return NO;

3,第三步:弱指針指向self,2s延遲,然后通過這個弱指針調(diào)用-assertNotDealloc,若被釋放,給nil發(fā)消息直接返回,不觸發(fā)-assertNotDealloc方法,認為已經(jīng)釋放;如果它沒有被釋放(泄漏了),-assertNotDealloc就會被調(diào)用

    __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,現(xiàn)在我們回到: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調(diào)用父類的willDealloc,即上面目錄4

2,第二步:調(diào)用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];
    }
}

通過代碼可以看出,通過調(diào)用willReleaseChildren的方法,獲取當前對象viewStack,parentPtrs,并且遍歷children,為每個子對象設置viewStack,parentPtrs,然后調(diào)用willDealloc。

通過源碼看一下viewStask,parentPtrs的實現(xiàn)

- (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使用數(shù)組,parentPtrs使用的集合形式。都是通過運行時,用關聯(lián)對象添加屬性。

parentPtrs會在-assertNotDealloc中,會判斷當前對象是否與父節(jié)點集合有交集。下面仔細看下-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的判斷是否有交集

產(chǎn)看其源碼:

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

可以看到,創(chuàng)建了一個單例對象,通過集合的形式,判斷是否有交集,是的話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 關聯(lián)一個代理即 proxy

第二步:通過objc_setAssociatedObject(object, kLeakedObjectProxyKey, proxy, OBJC_ASSOCIATION_RETAIN)方法,object強持有proxy, proxy若持有object,如果object釋放,proxy也會釋放

第三步:存儲 proxy.objectPtr(實際是對象地址)到集合 leakedObjectPtrs 里邊

第四步:彈框 AlertView若 _INTERNAL_MLF_RC_ENABLED == 1,則彈框會增加檢測循環(huán)引用的選項;若 _INTERNAL_MLF_RC_ENABLED == 0,則僅展示堆棧信息。

對于MLeakedObjectProxy類而言,是檢測到內(nèi)存泄漏才產(chǎn)生的,作為泄漏對象的屬性存在的,如果泄漏的對象被釋放,那么MLeakedObjectProxy也會被釋放,則調(diào)用-dealloc函數(shù)

集合leakedObjectPtrs中移除該對象地址,同時再次彈窗,提示該對象已經(jīng)釋放了

6,自己也在嘗試重寫該框架,歡迎大家一起交流

以上就是IOS內(nèi)存泄漏檢查方法及重寫MLeakFinder的詳細內(nèi)容,更多關于IOS內(nèi)存泄漏的資料請關注腳本之家其它相關文章!

相關文章

最新評論