相信大家都有過重寫 dealloc
方法來檢查某個 view controller 在消失后是否被釋放的經(jīng)歷棍厌。這幾乎是 iOS 中尋找由于引用循環(huán)造成內(nèi)存泄漏最有效的方法了。基本上每次發(fā)布蒜危,都會做很多次這種事情。不得不說這件事情很無聊,并且很可能會出錯警没。如果我們在日常的開發(fā)中, 提前的學習相關(guān)的知識, 那該多好?
下面是兩個很少見的 UIViewController
的屬性:
-
isBeingDismissed
當一個模態(tài)推送出來的 view controller 正在消失的時候, 為: true. -
isMovingFromParentViewController
,當一個 view controller 正在從它的父 view contrlller 中移除的時候(包括從系統(tǒng)的容器試圖比如說 UINavigationController), 為true.
如果這兩個屬性有一個是 true
的話, 這個 view controller 就會自動的被釋放掉振湾。我們不知道一個 view contrller 完成內(nèi)部狀態(tài)的改變杀迹,并且被 ARC 釋放掉需要耗費多長的時間。為了簡單起見押搪,我們假設(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]
來避免這個閉包強引用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)引用娃豹。
- 另外在 twitter 上也看到了類似的解決方案。
![](https://pbs.twimg.com/media/DH_nWyhXUAIqByQ.png)
3.使用國人寫的 MLeaksFinder 在每次發(fā)生內(nèi)存泄漏的時候都會彈窗购裙。并且沒有代碼侵入性懂版,只需要使用 CocosPod 導入就可以了。
4.在使用圖片資源的時候躏率,少使用 imageNamed:
方法去獲取使用頻次不高的圖片資源躯畴。因為使用 imageNamed:
加載的圖片資源會一直存在內(nèi)存里面民鼓, 對內(nèi)存的浪費也是巨大的。
5.上面的方法寫了一個 OC 版本的:
.h:
#import <UIKit/UIKit.h>
@interface UIViewController (FindLeaks)
// 默認為 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 就好了蓬抄。