问题描述
在做项目时遇到一个闪退问题,查看代码逻辑发现以下代码会造成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,以免出现不可预测的闪退问题。