常常会用到 ViewController,可是对它的生命周期一直没有一个比较完整地理解,最近看了几篇博客,在这里对 ViewConroller 的生命周期作一个总结,一是为了本身学习,二是为了给你们一个参考,若有错误,欢迎指正。ios
ViewController 整体生命周期:app
经过 xib 加载函数
先看一下 Demo 的文件结构,ViewController 为 A 控制器,TestViewController 为 B 控制器。布局
当控制器 view 经过 xib 加载的时候,可能会出现三种状况:学习
指定xib名称(OtherViewController.xib)动画
TestViewController *testVC = [[TestViewController alloc] initWithNibName:@"OtherViewController" bundle:nil];
复制代码
当咱们指定了xib的名称,loadView方 法就会去加载对应的 xib (OtherViewController.xib),最终是这个样子的。spa
不指定 xib 名称13d
TestViewController *testVC = [[TestViewController alloc] initWithNibName:nil bundle:nil];
复制代码
若是咱们不指定 xib 名称,loadView 就会加载与控制器同名的 xib (TestViewController.xib),最终是这个样子的。code
不指定 xib 名称2cdn
咱们先将 TestViewController.xib 这个文件删除掉,这个时候,咱们再来运行程序,结果是这样的。
根据上图咱们能够得知,当没有指定 xib 名称,且没有与控制器同名的 xib 时,会加载前缀与控制器名相同而不带 Controller 的 xib (TestView.xib)。
init
咱们常常会用代码经过 init 手动建立一个 ViewController,以下:
TestViewController *testVC = [[TestViewController alloc] init];
复制代码
其实本质仍是调用了 initWithNibName:bundle: 而且都传入了 nil,只不过以上三种状况都没有知足,最终是这个样子的。
当你从 storyboard 初始化 ViewController 时,iOS 会使用 initWithCoder,而不是 initWithNibName 来初始化这个 ViewController,而后那个 storyboard 会在本身内部生成一个 nib (storyboard 实例化 view / ViewController 时,会把 nib 的信息放在 Coder 中,调用 initWithCoder)。
注意
storyboard 加载的是控制器及控制器 view,而 xib 加载的仅仅只是控制器的 view。之因此这么说,咱们结合控制器的 awakeFromNib 方法解释一下,顾名思义,当控制器从 nib 加载的时候就会调用这个方法,这个方法自己只是个信号、消息,是一个空方法 (即其默认实现为空)。
先来看看经过 storyboard 加载的状况:
//A控制器中代码
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"TestViewController" bundle:nil];
TestViewController *testVC = [storyboard instantiateInitialViewController];
[self.navigationController pushViewController:testVC animated:YES];
}
//B控制器中代码
- (void)awakeFromNib {
NSLog(@"B经过nib加载");
}
复制代码
调用了 B 控制器的 awakeFromNib 方法。
将以前删除的 TestViewController.xib 文件重写添加进去,再来看经过xib加载的状况:
//A控制器中代码改成以下
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
TestViewController *testVC =[[TestViewController alloc] init];
[self.navigationController pushViewController:testVC animated:YES];
}
//B控制器中代码不变
复制代码
B 控制器的 awakeFromNib 方法并无被调用。
因此,storyboard 加载的是控制器及控制器 view,而 xib 加载的仅仅只是控制器的 view。
这个方法中,要正式加载View了。首先咱们得知道,控制器 view 是经过懒加载的方式进行加载的,即用到的时候再加载。永远不要主动调用这个方法。当咱们用到控制器 view 时,就会调用控制器 view 的 get 方法,在 get 方法内部,首先判断 view 是否已经建立,若是已存在,则直接返回存在的 view,若是不存在,则调用控制器的 loadView 方法,在控制器没有被销毁的状况下,loadView 也可能会被执行屡次。
当 ViewController 有如下状况时都会在此方法中从 nib 文件加载 view :
符合以上三点时,也就不须要重写这个方法,不然你没法获得你想要的 nib 中的 view。
若是这个 ViewController 与 nib 无关,你能够在这里手写 ViewController 的 view (这一步大概也能够在 viewDidLoad 里写,实际上咱们也更常在 viewDidLoad 里写)。
是否须要调用 [super loadView]
loadView 方法的默认实现是这样:先寻找有关可用的 nib 文件的信息,根据这个信息来加载 nib 文件,若是没有有关 nib 文件的信息,默认实现会建立一个空白的 UIView 对象,而后让这个对象成为 controller 的主 view。
因此,重写这个函数时,你也应该这么作。并把子类的 view 赋给 view 属性 (property) (你 create 的 view 必须是惟一的实例,而且不被其余任何 controller 共享)。
若是你要进行进一步初始化你的 views,你应该在 viewDidLoad 函数中去作。在iOS 3.0 以及更高版本中,你应该重载 viewDidUnload 函数来释听任何对 view 的引用或者它里面的内容(子 view 等等)。
回到关于 [super loadView] 的讨论中,若是咱们的 ViewController 与 nib 有关,也就是说咱们不须要重写 loadView 方法,也就不用关心 [super loadView]。而若是与 nib 无关,咱们须要重写 loadView 方法,而 [super loadView] 根据上面的解释就会生成一个空白的 view,这恐怕并不能知足咱们的需求,因此调用也没有多大意义。
从图中能够看到,在 view 加载过程当中首先会调用 loadView 方法,在这个方法中主要完成一些关键 view 的初始化工做,好比 UINavigationViewController 和 UITabBarController 等容器类的 ViewController;接下来就是加载 view,加载成功后,会接着调用 viewDidLoad 方法,这里要记住的一点是,在 loadView 以前,是没有 view 的,也就是说,在这以前,view 尚未被初始化。完成 viewDidLoad 方法后,ViewController 里面就成功的加载 view了,如上图右下角所示。
死循环
若 loadView 没有加载 view,即为 nil,viewDidLoad 会一直调用 loadView 加载 view,所以构成了死循环,程序即卡死,因此咱们常在 ViewDidLoad 里建立 view。
从图中能够看到,当系统发出内存警告时,会调用 didReceiveMemoeryWarning 方法,若是当前有能被释放的 view,系统会调用 viewWillUnload 方法来释放 view,完成后调用 viewDidUnload方法,至此,view 就被卸载了。此时本来指向 view 的变量要被置为 nil,具体操做是在 viewDidUnload 方法中调用 self.myButton = nil。
当控制器的 loadView 方法执行完毕,view 被建立成功后,就会执行 viewDidLoad 方法,该方法与loadView 方法同样,也有可能被执行屡次。在开发中,咱们可能从未遇到过执行屡次的状况,那何时会执行屡次呢?
好比 A 控制器 push 出 B 控制器,此时,窗口显示的是 B 控制器的 view,此时若是收到内存警告,咱们通常会将 A 控制器中没用的变量及 view 销毁掉,以后当咱们从 B 控制器 pop 到 A 控制器时,就会再次执行A控制器的 loadView 方法与 viewDidLoad 方法。
viewWillAppear 老是在 viewDidLoad 以后被调用,但不是当即,当你只是引用了属性 view,却没有当即把 view 添加到任何已经展现的视图上时,viewWillAppear 不会被调用,这在 view 被外部引用时,就会发生。固然,随着 ViewController 的屡次推入,屡次进入子页面后返回,该方法会被屡次调用。与 viewDidLoad 不一样,调用该方法就说明控制器必定会显示。
锁屏以后会被调用吗?
不会。viewWillAppear 关注的是 view 在层次中的显示与消失,锁屏并无改变 App 自己的层次。
Window叠加后,会被调用吗?
不会。同锁屏时的缘由相似,叠加 Window 并无改变 ViewController 所在 Window 的视图层次,换句话说,view 并无被覆盖或删除 (相对于本身所在 Window)。
注意
若是控制器 A 被展现在另外一个控制器 B 的 popover 中,那么控制器 B 不会调用该方法,直到控制器 A 清除。
视图已在屏幕上渲染完成。子视图有自定义动画时,建议在 Did 方法中启动,在 Will 中启动动画时,动画效果将不会很理想。
如下两个方法将会被调用:
- viewWillLayoutSubviews
- viewDidLayoutSubviews
复制代码
viewWillLayoutSubviews
该方法在通知控制器将要布局 view 的子控件时调用。每当视图的 bounds 改变,view 将调整其子控件位置。默认实现为空,可重写以在 view 布局子控件前作出改变。该方法调用时,AutoLayout 未起做用。
viewDidLayoutSubviews
该方法在通知控制器已经布局 view 的子控件时调用。默认实现为空,可重写以在 view 布局子控件后作出改变。该方法调用时,AutoLayout 未起做用。
注意
使用 Autolayout 时,子视图大小只有在 viewDidLayoutSubviews 才真正被设置好,因此这里才是获取子视图大小的正确位置,常见的错误是,在 viewDidLoad 中读取了某个 view.frame,用来给其它子视图赋值,结果获得一堆大小“不定”的视图,甚至可能为零,在视图中看不见!
viewWillDisappear
该方法在控制器 view 将要从视图层次移除时被调用,可重写以提交变动,取消视图第一响应者状态。
viewDidDisappear
该方法在控制器 view 已经从视图层次移除时被调用,可重写以清除或隐藏控件。
二者配套调用,具体指子视图控制器是以 push 和 present 方法显示的,父视图控制器的以上两个方法会被触发。
特别的,addSubview 会调用子控制器 Appear 系列方法,但不会调用父视图 viewWillDisappear 方法。
以下添加子视图:
XSDViewController *subVC = [[XSDViewController alloc] init];
[self addChildViewController:subVC];
[subVC.view setFrame:self.view.frame];
[self.view addSubview:subVC.view];
[subVC didMoveToParentViewController:self];
复制代码
获得结果是,只有 XSDViewController 的 Appear 系列方法被调用,这样的调用与 push / present 方法根本不一样是父视图的 view 没有“隐藏”,只是被覆盖了。
当系统内存不足时,首先 ViewController 的 didReceiveMemoryWarining 方法会被调用,而 didReceiveMemoryWarining 会判断当前 ViewController 的 view 是否显示在 window 上,若是没有显示在 window 上,则 didReceiveMemoryWarining 会自动将 ViewController 的 view 以及其全部子 view 所有销毁,而后调用 viewcontroller 的 viewdidunload 方法。若是当前 ViewController 的 view 显示在 window 上,则不销毁该 ViewController 的 view,固然,viewDidunload 也不会被调用了。
iOS 升级到 6.0 之后,再也不支持 viewDidUnload 了。官方文档的解释是系统会自动控制大的 view 所占用的内存,其余小的 view 所占用的内存是极其微小的,不值得为了省内存而去清理而后在从新建立。若是你须要在内存警告的时候释放业务数据或者作些其余的特定处理,你能够实现 didReceiveMemoryWarning 这个函数。
iOS 6.0 及以上版本的内存警告处理方法:
-(void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];//即便没有显示在window上,也不会自动的将self.view释放。
// Dispose of any resources that can be recreated.
// 此处作兼容处理须要加上ios6.0的宏开关,保证是在6.0下使用的,6.0之前屏蔽如下代码,不然会在下面使用self.view时自动加载viewDidUnLoad
if ([[UIDevice currentDevice].systemVersion floatValue] >= 6.0) {
//须要注意的是self.isViewLoaded是必不可少的,其余方式访问视图会致使它加载 ,在WWDC视频也忽视这一点。
if (self.isViewLoaded && !self.view.window) {// 是不是正在使用的视图
//code
self.view = nil;// 目的是再次进入时可以从新加载调用viewDidLoad函数。
}
}
}
复制代码
当发出内存警告调用 viewDidUnload 方法时,只是释放了 view,并无释放 ViewController,因此并不会调用 dealloc 方法。即 viewDidUnload 和 dealloc 方法并无任何关系,dealloc 方法只会在 ViewController 被释放的时候调用。
当 .nib 文件被加载的时候,会发送一个 awakeFromNib 的消息到 .nib 文件中的每一个对象,每一个对象均可以定义本身的 awakeFromNib 方法来响应这个消息,执行一些必要的操做。也就是说经过 nib 文件建立 view 对象时执行 awakeFromNib。
看完文档继续补充。
当咱们点击 push 的时候首先会加载下一个界面而后才会调用界面的消失方法。
当在一个控制器内 Push / Present 新的控制器,原先的控制器并不会销毁,但会消失,所以调用了 viewWillDisappear 和 viewDidDisappear 方法。
若是控制器 A 被展现在另外一个控制器 B 的 popover 中,那么控制器 B 不会调用 viewWillAppear 方法,直到控制器 A 清除。这时,控制器 B 会再一次出现,所以调用了其中的 viewWillAppear 和 viewDidAppear 方法。