一个和viewController生命周期相关的bug
最近在项目的开发过程中,遇到一个隐藏的关于viewController生命周期相关的bug,我已经写了一个demo把这个问题复现,可以点击demo查看。
问题复现:
1.创建一个controller,给controller,添加两个属性
@interface LoadViewController : UIViewController
@property (nonatomic, strong) UIView *centerView;
@property (nonatomic, strong) NSString *testString;
@end
在controller的实现文件中,写以下几个方法
- (void)loadView {
[super loadView];
}
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.centerView];
NSLog(@"f == %p",self.centerView);
self.view.backgroundColor = [UIColor whiteColor];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(@"t == %p",self.centerView);
NSLog(@"subviews == %@",self.view.subviews);
UIView *temp = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 10, 10)];
temp.backgroundColor = [UIColor orangeColor];
[self.centerView addSubview:temp];
}
#pragma mark - Setter
- (void)setTestString:(NSString *)testString {
_testString = testString;
self.centerView.frame = CGRectMake(0, 0, 20, 20);
NSLog(@"s == %p",self.centerView);
}
#pragma mark - Getter
- (UIView *)centerView {
if (nil == _centerView) {
_centerView = [[UIView alloc]initWithFrame:CGRectMake(0, 150, self.view.bounds.size.width, 50)];
_centerView.backgroundColor = [UIColor grayColor];
}
return _centerView;
}
在viewDidLoad方法中,把centerView添加到self.view上,然后在viewWillAppear中在centerView上添加一个橙色的小块,在testString的set方法中,把centerView的大小改为宽高20,controller的创建如下:
LoadViewController *ctl = [[LoadViewController alloc]init];
ctl.testString = @"test";
[self.navigationController pushViewController:ctl animated:YES];
运行之后发现centerview的大小并没有改变为宽高20,那个橙色的小色块也并没有添加到centerview中,那么是哪里出了问题呢?
在不同的地方打印如下:
2016-04-01 09:52:44.588 LoadViewDemo[55368:6620177] f == 0x7ffaf2c264e0
2016-04-01 09:52:44.588 LoadViewDemo[55368:6620177] s == 0x7ffaf2c29900
2016-04-01 09:52:44.590 LoadViewDemo[55368:6620177] t == 0x7ffaf2c29900
2016-04-01 09:52:44.590 LoadViewDemo[55368:6620177] subviews == (
"<UIView: 0x7ffaf2c264e0; frame = (0 150; 375 50); layer = <CALayer: 0x7ffaf2c16da0>>"
)
我们发现,centerview在不同的时期,有两个不相同的地址,属性self.centerView的地址为0x7ffaf2c29900 而添加到self.view的centerview的内存地址为0x7ffaf2c264e0,为什么会出现这种情况呢,短短几行代码,竟然出现了两个centerview 。通过断点调试发现,第一次centerview创建时,在执行完 centerView = [[UIView alloc]initWithFrame:CGRectMake(0, 150, self.view.bounds.size.width, 50)] 这一行时,然后调转到了去执行viewDidLoad,然后接着回来执行了 centerView.backgroundColor = [UIColor grayColor] 这一行,然后竟然又执行了 _centerView = [[UIView alloc]initWithFrame:CGRectMake(0, 150, self.view.bounds.size.width, 50)] 这一行 ,仔细观察这行代码,发现只有self.view.bounds.size.width可能不正常,把它换掉,果然问题不再出现。
问题找到了,要了解问题为什么出现,我们需要好好看一下self.view.bounds.size.width这个代码,到底做了什么。
问题原因:
当调用ctl.testString = @”test”时,会self.centerView.frame = CGRectMake(0, 150, 50, 100),此时会去调用
- (UIView *)centerView {
if (nil == _centerView) {
_centerView = [[UIView alloc]initWithFrame:CGRectMake(0, 150, self.view.bounds.size.width, 50)];
_centerView.backgroundColor = [UIColor grayColor];
}
return _centerView;
}
来创建centerview,这时self.view是空的,当调用self.view并且self.view为空时,会调用loadView方法,之后会调用viewDidLoad方法,而在viewDidLoad方法中,我们又再次调用了上面的创建cenerview的get方法,由于此时上一次创建并没有完成,所以此时会再次重新创建一个。真相大白
TipS
现在我们来看一下loadView的释义
The view controller calls this method when its view property is requested but is currently nil. This method loads or creates a view and assigns it to the view property.
当使用self.view且self.view为空时,就会触发loadView方法。
viewDidLoad的释义
this method is called after the view controller has loaded its view hierarchy into memory
当loadView调用完成后,变会调用viewDidLoad
原来如此