ViewController建立後釋放閃退
問題描述
在做專案時遇到一個閃退問題,檢視程式碼邏輯發現以下程式碼會造成crash。
- (IBAction)buttonTouchUpInside:(id)sender {
TestTableViewController *vc = [[TestTableViewController alloc]init];
}
是的,你沒有看錯,上面的程式碼會造成閃退,TestTableViewController的程式碼如下:
TestTableViewController.h檔案
#import <UIKit/UIKit.h>
@interface TestTableViewController : UITableViewController
@end
TestTableViewController.m檔案
#import "TestTableViewController.h"
@interface TestTableViewController ()
@end
@implementation TestTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
__weak TestTableViewController *weakSelf = self;
[self.tableView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
}
- (void)dealloc {
[self.tableView removeObserver:self forKeyPath:@"contentOffset"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id > *)change context:(void *)context {
NSLog(@"%@",change);
}
@end
出現閃退的程式碼是:
__weak TestTableViewController *weakSelf = self;
我看到這裡的第一感覺是很意外,因為呼叫的地方只是init了一個例項,隨後該例項作為方法中的臨時變數應該就自動釋放了,所以不應該會呼叫到- (void)viewDidLoad
方法中的程式碼。
但是,TestTableViewController作為UITableViewController的子類,當在- (void)dealloc
方法中,訪問父類的self.tableView時,就觸發了- (void)viewDidLoad
方法。
隨後,在執行__weak TestTableViewController *weakSelf = self;
程式碼時閃退。
在iOS8.4版本的模擬器上錯誤日誌是EXC_BAD_INSTRUCTION(code=EXC_I386_INVOP,subcode=0x0)
。
而後我發現該問題在iOS10系統中並不會閃退,而只是在執行時輸出一個warning問題:
[Warning] Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior (<TestTableViewController: 0x7fe85fc01790>)
但是,如果呼叫的地方是這樣的:
- (IBAction)buttonTouchUpInside:(id)sender {
TestTableViewController *vc = [[TestTableViewController alloc]init];
[self presentViewController:vc animated:YES completion:^{
[vc dismissViewControllerAnimated:YES completion:^{
}];
}];
}
present顯示vc後馬上dismiss掉,這樣並不會出現閃退問題。
也就是說只要TestTableViewController的viewDidLoad方法執行過,那麼在dealloc的時候便不會再觸發viewDidLoad方法。
解決方法
該問題發生有三個條件:
1、在dealloc
方法中訪問了父類的tableView屬性。
2、在viewDidLoad
方法中定義了weakSelf變數。
3、建立了TestTableViewController的例項變數,但又沒有使用。
以上三個條件,前兩個都是業務邏輯的要求,一般來說無法避免,所以只能儘可能避免第3個條件的出現,即:ViewController的例項,如果用不到就不要建立。
比如:
- (void)showWithType:(NSInteger)type {
TestTableViewController *vc = [[TestTableViewController alloc]init];
if (type==1) {
vc.title = @"1";
} else if (type==2) {
vc.title = @"2";
} else {
return;
}
[self presentViewController:vc animated:YES completion:nil];
}
- (void)showWithType2:(NSInteger)type {
NSString *title;
if (type==1) {
title = @"1";
} else if (type==2) {
title = @"2";
} else {
return;
}
TestTableViewController *vc = [[TestTableViewController alloc]init];
vc.title = title;
[self presentViewController:vc animated:YES completion:nil];
}
儘量使用showWithType2方法的寫法,而不是showWithType,以免出現不可預測的閃退問題。