iOS 內(nèi)存泄漏三兩事

相信大家都有過重寫 dealloc 方法來檢查某個 view controller 在消失后是否被釋放的經(jīng)歷。這幾乎是 iOS 中尋找由于引用循環(huán)造成內(nèi)存泄漏最有效的方法了?;旧厦看伟l(fā)布,都會做很多次這種事情。不得不說這件事情很無聊,并且很可能會出錯。如果我們在日常的開發(fā)中, 提前的學(xué)習(xí)相關(guān)的知識, 那該多好?

下面是兩個很少見的 UIViewController的屬性:

  • isBeingDismissed 當(dāng)一個模態(tài)推送出來的 view controller 正在消失的時候, 為: true.
  • isMovingFromParentViewController ,當(dāng)一個 view controller 正在從它的父 view contrlller 中移除的時候(包括從系統(tǒng)的容器試圖比如說 UINavigationController), 為true.

如果這兩個屬性有一個是 true 的話, 這個 view controller 就會自動的被釋放掉。我們不知道一個 view contrller 完成內(nèi)部狀態(tài)的改變,并且被 ARC 釋放掉需要耗費(fèi)多長的時間。為了簡單起見,我們假設(shè)它不會超過兩秒。

1.現(xiàn)在看看下面的代碼(文末會有OC版):

extension UIViewController {
    public func dch_checkDeallocation(afterDelay delay: TimeInterval = 2.0) {
        let rootParentViewController = dch_rootParentViewController

        // We don’t check `isBeingDismissed` simply on this view controller because it’s common
        // to wrap a view controller in another view controller (e.g. in UINavigationController)
        // and present the wrapping view controller instead.
        if isMovingFromParentViewController || rootParentViewController.isBeingDismissed {
            let type = type(of: self)
            let disappearanceSource: String = isMovingFromParentViewController ? "removed from its parent" : "dismissed"

            DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: { [weak self] in
                assert(self == nil, "\(type) not deallocated after being \(disappearanceSource)")
            })
        }
    }

    private var dch_rootParentViewController: UIViewController {
        var root = self

        while let parent = root.parent {
            root = parent
        }

        return root
    }
}

在延時操作這個閉包中,我們首先通過 [weak self] 來避免這個閉包強(qiáng)引用self。然后通過斷言讓程序在 self 不為空的時候拋出異常。只有存在循環(huán)引用的情況下這個 view controller 才不為空。

現(xiàn)在我們需要做的就是在 viewDidDisappear 中調(diào)用這個方法。只要是你需要檢查它在消失后是不是被釋放掉的 view controller 都需要添加這個方法。

override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)

    dch_checkDeallocation()
}

如果發(fā)聲了內(nèi)存泄漏,我們就會得到下面的斷言:

這個時候,我們只需要打開 Xcode 的 Memory Graph Debugger 找到并且解決這些循環(huán)引用。

  1. 另外在 twitter 上也看到了類似的解決方案。

3.使用國人寫的 MLeaksFinder 在每次發(fā)生內(nèi)存泄漏的時候都會彈窗。并且沒有代碼侵入性,只需要使用 CocosPod 導(dǎo)入就可以了。

4.在使用圖片資源的時候,少使用 imageNamed: 方法去獲取使用頻次不高的圖片資源。因為使用 imageNamed:加載的圖片資源會一直存在內(nèi)存里面, 對內(nèi)存的浪費(fèi)也是巨大的。

5.上面的方法寫了一個 OC 版本的:

.h:

#import <UIKit/UIKit.h>

@interface UIViewController (FindLeaks)


// 默認(rèn)為 NO
@property (nonatomic) BOOL noCheckLeaks;

@end

.m:

//
//  UIViewController+FindLeaks.m
//  Leaks
//
//  Created by sunny on 2017/8/27.
//  Copyright ? 2017年 CepheusSun. All rights reserved.
//

#import "UIViewController+FindLeaks.h"
#import <objc/runtime.h>

static const char *noCheckLeaksKey = "noChechLeaksKey";

@interface NSObject (MethodSwizzling)

+ (void)sy_swizzleInstanceSelector:(SEL)origSelector
                   swizzleSelector:(SEL)swizzleSelector;

@end

@implementation UIViewController (FindLeaks)

#pragma mark - Binding Property
- (BOOL)noCheckLeaks {
    return [objc_getAssociatedObject(self, noCheckLeaksKey) boolValue];
}

- (void)setNoCheckLeaks:(BOOL)noCheckLeaks {
    objc_setAssociatedObject(self, noCheckLeaksKey, [NSNumber numberWithBool:noCheckLeaks], OBJC_ASSOCIATION_RETAIN_NONATOMIC);

}

#pragma mark - Check
+ (void)load {
    
#if DEBUG
    [self sy_swizzleInstanceSelector:@selector(viewDidDisappear:) swizzleSelector:@selector(fl_viewDidDisappear:)];
#endif
}

- (void)fl_viewDidDisappear:(BOOL)animated {
    [self fl_viewDidDisappear:animated];
    if (!self.noCheckLeaks) {
        [self fl_checkDeallocationAfterDelay:2];
    }
}

- (void)fl_checkDeallocationAfterDelay:(NSTimeInterval)delay {
    UIViewController *root = [self fl_rootParentViewController];
    if (self.isMovingFromParentViewController || root.isBeingDismissed) {
        NSString *type = NSStringFromClass([self class]);
        NSString *disappearanceSource = self.isMovingFromParentViewController ? @"removed from its parent" : @"dismissed";
        __weak typeof(self) weakSelf = self;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSString *assert = [NSString stringWithFormat:@"%@ not deallocated after being %@",
             type, disappearanceSource];
            NSAssert(weakSelf == nil,assert);
        });
    }
}

- (UIViewController *)fl_rootParentViewController {
    UIViewController *root = self;
    while (root.parentViewController) {
        root = root.parentViewController;
    }
    return root;
}

@end

@implementation NSObject (MethodSwizzling)

+ (void)sy_swizzleInstanceSelector:(SEL)origSelector
                   swizzleSelector:(SEL)swizzleSelector {
    
    Method origMethod = class_getInstanceMethod(self, origSelector);
    Method swizzleMethod = class_getInstanceMethod(self, swizzleSelector);
    
    BOOL isAdd = class_addMethod(self, origSelector, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));
    
    if (!isAdd) {
        method_exchangeImplementations(origMethod, swizzleMethod);
    }else {
        class_replaceMethod(self, swizzleSelector, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
    }
}


@end

只需要在不需要檢查的方法中設(shè)置屬性為 YES 就好了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容