UIViewController的生命周期
单纯为了做项目你要不太了解底层的知识可能对你项目的实现影响不是很大(前提是不追求极致的项目😆😆)。但是如果不知道点UIViewController的知识你想做项目那就有点难了。 因为做的项目离不开各种各样的UIViewController,所以我就带你聊聊UIViewController的生命周期也就是它的 “初始化” 到 "释放" 的过程。
前言:
iOS中最离不开的就是UIViewController,为啥它这么重要就不介绍了。
生命周期简单概括有如下几步
初始化 --> 加载视图 -->配置视图 -->显示 -->消失 -->销毁
初始化方式
- xib文件
- 纯代码
- storyboard加载
- (instancetype)init (这个在UIViewController的API中是没列出的)
- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil ;
- (instancetype)initWithCoder:(NSCoder *)aDecoder ;
以上代码是官方API罗列出来的初始化方法下面一一介绍
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil ;
- nibNameOrNil --> nib的名字
- nibBundleOrNil --> 查找nib文件的bundle名字,一般指定为nil 进行自动搜索
该方法的使用也有几个情况
项目结构
-
情况一
BBBViewController *VC = [[BBBViewController alloc] initWithNibName:@"BBBViewController" bundle:nil];
该情况也是大家的通常做法,就是nib的名字跟控制器的类名 “一致“ 看一下效果。
运行代码效果-
代码运行效果情况二
BBBViewController *VC = [[BBBViewController alloc] initWithNibName:@"OtherViewController" bundle:nil];
该情况是nib的名字跟控制器的类名 ”不一致“ 看一下效果。
-
情况三
BBBViewController *VC = [[BBBViewController alloc] initWithNibName:nil bundle:nil];
参数指定为nil的时候它的加载情况有3种:
- 如果有跟控制器类名一致的(也就是BBBViewController.xib)就加载该文件,这个效果跟上面介绍的(情况一)是一样的。
- 如果没有跟控制器类名一致的xib文件的时候,也会自动匹配项目中的xib文件。但这个xib文件的名字一定是控制器类名去除掉Controller之后剩下的名字(例:BBBViewController控制器,那么会自动匹配BBBView.xib文件),其中这个场景有一个关键点如果想以这样的方式匹配xib文件那么你的控制器的名字一定是以ViewController结尾的(***ViewController)否则不能匹配到这个情况。
- 如果项目中没有上述的2个情况,那么控制器就会无xib加载也就是创建一个空的view
- (instancetype)init;
这个方法跟调用BBBViewController *VC = [[BBBViewController alloc] initWithNibName:nil bundle:nil];效果一样
唯一一个区别就是这个方法会首先调用一下 initWithNibName:bundle:方法然后再调用一次alloc] init]方法(initWithNibName:bundle:初始化控制器时候不走该方法)
- (instancetype)initWithCoder:(NSCoder *)aDecoder ;
如果ViewController是从storyboard初始化的话,那么会执行上面这个方法,但是跟加载xib文件有一个不同就是从storyboard加载VC的时候还会走 - (void)awakeFromNib 这个方法
总结
- 通常我们需要做的是在ViewController的初始化阶段对用到的数据进行初始化的操作,切记不要在这个位置对view进行操作(例如self.view),因为这个时候VC还没有初始化完成哪来的view, 如果这个时候手动调用的话会导致整个VC的流程出错。
- 通过上面的介绍知道VC的初始化代码的几种方式,尤其是加载xib的时候的情况。不过为了可阅读性还是建议xib的文件名跟VC的类名保持一致。
- (void)loadView
初始化完成就要真正的加载VC的view了,VC的view加载是一种懒加载的方式,用到的时候会自动调用。所以不要手动调用该方法,这个方法在VC的整个过程中可能会被调用多次。
有2种情况我们是一定不要重写这个方法的
- view是从xib文件中加载的
- view是从storyboard中加载
以上两种情况如果你重写了loadView方法那么你的文件上的布局统统作废
总结: 一般我们是不需要重写这个方法的,除非你对VC的view有特殊的要求,系统默认是给我创建一个空的view作为VC的视图。如果你自己想创建了一个view作为VC的view,那么一定要把自定义的view赋值给VC.view否则将认为VC的view是空值,这样会使VC陷入死循环。
- (void)viewDidLoad
当VC的view被创建完成之后也就是执行完loadView以后执行该方法。通常我们需要在这个方法里面来配置UI。因为这个时候view是确确实实的存在了,可以对其进行一些子视图的添加等。
注意:
该方法通常是执行一次,但其实他跟loadView这个方法一样也存在执行多次的情况,通过上面的介绍我们知道如果这个界面的view被重新加载那么一定顺其自然的执行该方法。
举例说明:A控制器 push B控制器 如果这时收到了内存警告随即会调用A控制器中的didReceiveMemoryWarning方法,如果你在A和B控制器的didReceiveMemoryWarning方法对没用的变量释放以及销毁其view的操作(self.view = nil) 那么当你从B返回A的时候因为A的view被释放了,所以还会重新走viewDidLoad的方法。这个场景主要取决于你对内存警告时候的处理。
- (void)viewWillAppear && - (void)viewDidAppear
这两个方法是成对的,所以一起介绍viewWillAppear正常是在viewDidLoad之后调用的,但是该方法不一定是在viewDidLoad之后立即被调用。调用该方法的时刻是view显示的时候,而且随着控制器Push / Present 以及对应的Pop / Dismiss 等方法会使该方法执行多次。
- 举例:
// 这个方法是不会调用viewWillAppear的,因为你只是初始化了VC并没有让view显示出来
BBBViewController *VC = [[BBBViewController alloc] init];
// 这个方法是不会调用viewWillAppear
// 虽然你调用了vc.view但没有加到可以显示的图层同样不用调用
BBBViewController *VC = [[BBBViewController alloc] init];
VC.view.frame = CGRectMake(100, 100, 100, 100);
// 这个方法是会调用viewWillAppear,因为view加载到了父视图上
BBBViewController *VC = [[BBBViewController alloc] init];
VC.view.frame = CGRectMake(100, 100, 100, 100);
[AAAVC.view addSubview:VC.view];
注意
viewWillAppear执行完了,未必会执行viewDidAppear,文章后面会举例说明。
- (void)viewWillLayoutSubviews && - (void)viewDidLayoutSubviews
来看一下官方给的解释
// Called just before the view controller's view's layoutSubviews method is invoked. Subclasses can implement as necessary.
- (void)viewWillLayoutSubviews ;
// Called just after the view controller's view's layoutSubviews method is invoked. Subclasses can implement as necessary.
- (void)viewDidLayoutSubviews ;
这2个方法也是成对的,这段英文的注释大家肯定是一目了然, 一个是在 VC.view 的layoutSubviews被调用之前,另一个是在layoutSubviews被调用之后。这里不介绍layoutSubviews方法的调用机制。
注意
如果你的布局使用autolayout的时候,我们可能需要在控制器中使用代码来获取一些视图的frame,这个时候就需要注意一定要在viewDidLayoutSubviews中获取,只有在viewDidLayoutSubviews中视图的frame才是准确的。
- (void)viewWillDisappear && - (void)viewDidDisappear
同样成对的2个方法,当界面消失的时候例如pop/dismiss 操作的时候会先后执行这两个方法。
BBBViewController *VC = [[BBBViewController alloc] init];
[self addChildViewController:VC];
VC.view.frame = CGRectMake(100, 100, 100, 100);
[self.view addSubview:VC.view];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[VC.view removeFromSuperview];
});
上面这一段代码虽然不是pop/dismiss 依然会执行这两个方法,所以这两个方法被调用的时机就是VC.view消失的时候 (消失不等于隐藏)
注意
viewWillDisappear执行完了,未必会执行viewDidDisappear,文章后面会举例说明。
- (void)didReceiveMemoryWarning
当系统的内存不足时,会调用didReceiveMemoryWarining方法,我们需要做的就是释放掉部分暂时不用的资源。例:
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
if (!self.view.window && self.isViewLoaded) {
self.view = nil; //释放掉view等操作
}
}
- (void)dealloc
该方法只有在 ViewController 彻底被释放的时候调用。例如pop/dismiss,如果VC中的代码不够规范引起了循环引用等问题,即使执行了pop/dismiss并且界面也消失了但并没有真的释放。要注意这个方面。
控制器之间跳转场景举例
举例说明:现在有AAA控制器 和 BBB控制器。模拟在AAA控制器push到BBB控制器中的场景。下面这段代码写在AAA控制器中:
BBBViewController *VC = [[BBBViewController alloc] initWithNibName:nil bundle:nil];
//BBBViewController *VC = [[BBBViewController alloc] init]; //使用这句代码初始化也可以
[self.navigationController pushViewController:VC animated:YES];
当我们执行AAA push到 BBB的时候
- initWithNibName:bundle:(BBBViewController执行的)
- viewDidLoad: (BBBViewController执行的)
- viewWillDisappear:(AAAViewController执行的,将要消失)
- viewWillAppear:(BBBViewController执行的)
- viewWillLayoutSubviews:(BBBViewController执行的)
- viewDidLayoutSubviews:(BBBViewController执行的)
- viewDidDisappear:(AAAViewController执行的,已经消失)
- viewDidAppear:(BBBViewController执行的)
注意:上面提到的消失不是指的销毁
当我们执行BBB pop返回到 AAA的时候
- viewWillDisappear:(BBBViewController执行的)
- viewWillAppear:(AAAViewController执行的)
- viewDidDisappear:(BBBViewController执行的)
- viewDidAppear:(AAAViewController执行的)
- dealloc:(BBBViewController执行的)
当我们在BBB界面 用手势向右拖动返回 AAA的时候
当我们在BBB界面手势拖动让BBB界面和AAA界面都出现在屏幕上,但最后松开还是停留在BBB界面上这个时候的执行代码有一点不同,来看一下:
- viewWillDisappear:(BBBViewController执行的,将要消失的)
- viewWillAppear:(AAAViewController执行的,要返回到A也就是将要出现了)
- viewWillDisappear:(AAAViewController执行的,因为我们最后停在B上所以刚才的A还要执行一次这个方法)
- viewDidDisappear:(AAAViewController执行的)
- viewWillAppear:(BBBViewController执行的,刚刚执行了将要消失所以现在重新出现要执行这个方法)
- viewDidAppear:(BBBViewController执行的,最终停留在B上)