UIViewController 相关生命周期总结

awakeFromNib

当 view 被从 Storyboard 或者 Nib 文件中加载出来时会调用这个方法,只会在所有对象被创建后调用。

在这里可以做一些额外的 「Set Up」的工作。

当这个方法被调用时代表所有 nib 文件中的对象已经被创建出来,所有的 IBOutletsAction 已经被建立。

此方法覆盖时需要调用 super 方法。默认的 super 方法里没有实现。

另外,由于是 Archive 并实例化对象,所以 View Controller 在初始化时调用的是 initWithCoder:。手动调用 init 则不会从 nib 文件里加载。

这个方法在执行 loadNibNamed: 一类的方法时就会被调用。

loadView()

View Controller 创建后需要加载 self.view 时会调用这个方法。此方法不应该被直接调用。

如果我们的界面是在 Storyboard 中创建的,那我们也不应该覆盖这个方法。

当 View Controller 有以下情况时都会在此方法中从 nib 文件加载 View :1. View Controller 是从 storyboard 中实例化的。 2. 通过 initWithNibName:bundle: 初始化。 3. 在 App Bundle 中有一个 nib 文件名称和本类名相同。

当我们调用 self.view 时,如果 self.view 非 nil,那么会直接调用该对象。如果为 nil,那么会调用 self.loadView() 创建一个 UIView 并将这个对象赋值给 self.view

在这个函数里调用 self.view 属性会造成死循环。因为访问 self.view 时发现该属性为空,会去调用 loadView() 方法,此时会造成死循环。

此方法覆盖时不该调用 super 方法。

viewDidLoad()

当 View Controller 的 View 被加载入后会调用这个方法,因此正常情况下只会调用一次。

viewDidLoad() 中可以对 View 进行设置,例如设定 UIButton 的内容等等。当 View 是从 nib 文件中加载出来时,可以在此设置。

此时 view 还没有被加入 view hierarchy 中,只是被加载入了内存中。在这里如果执行 self.presentViewController 之类的操作会出错。

此方法覆盖时需要调用 super 方法。

死循环问题

如果 self.view 为空则会再去调用 loadView()。以下情况则会发生死循环:

var str = "loadView"

override func loadView() {  
    //没有实例化 self.view
    str = "loadView"
    print(str)
}

override func viewDidLoad() {  
    //调用 self.view
    str = "viewDidLoad"
    print(str)
    let newView = self.view
}

会发现控制台不停的输出:

loadView viewDidLoad
loadView
viewDidLoad
loadView
viewDidLoad

并且此时 newViewnil

如果在 loadView() 中没有加载 self.view,并且在 viewDidLoad() 中没有调用 self.view 时不会发生死循环,但也会将二者重复调用几次 TODO。(即去掉上面代码中 let newView = self.view 一句)。输出如下:

loadView viewDidLoad
loadView
viewDidLoad
loadView
viewDidLoad
loadView
viewDidLoad
loadView
viewDidLoad

如果在 loadView() 中实例化了 self.view,则输出是正常调用:

loadView viewDidLoad

viewWillAppear

当 View 将要被添加到 View Hierarchy 中时会调用这个方法,每一次 View 将要显示时都会调用。在这个方法被调用时,也是在显示 View 所需要的动画被配置前。

这个时候在做一些和 frame 相关的操作时仍会出错,在这里 View 将要被加入 View Hierarchy,但是仍旧没有被添加进去。

此方法覆盖时需要调用 super 方法。

viewWillLayoutSubviews

ViewController.view 将要布局 Subviews 时调用。当每一次界面的布局发生变化时都会被调用,例如旋转、被标记为需要 layout。

在这之后 AutoLayout 会改变布局。

viewDidLayoutSubviews

已经布局完成,也可以做一些操作。

已通过 AutoLayout 布局。

viewDidAppear

此时界面已经被显示出来了,做一些操作时可能会让界面变化可见。

AutoLayout 布局在各个时期的 Frame

通过 AutoLayout 在界面中添加一个 UITextView,在各个阶段输出它的 Frame 结果如下:

viewDidLoad

frame = (20 40; 560 160);  
contentSize: {560, 133};  
contentOffset: {0, 0};  

viewWillAppear

frame = (20 40; 560 160);  
contentOffset: {0, 0};  
contentSize: {560, 133};  

viewWillLayoutSubviews

frame = (20 40; 560 160);  
contentOffset: {0, 0};  
contentSize: {560, 133};  

viewDidLayoutSubviews

frame = (20 40; 374 296);  
contentOffset: {0, -15};  
contentSize: {374, 184}  

viewDidAppear

frame = (20 40; 374 296);  
contentOffset: {0, -15};  
contentSize: {374, 184};  

可以看到,从 viewDidLayoutSubview 开始, UITextViewFrame 发生了一次变化, contentSizecontentOffset 都发生了变化。

因为在 viewWillAppear 及之前, view 还没有被加入层级中,布局还不是最终布局。

所以在 viewWillAppear 及以前,计算 UITextView 行高等操作可能会出现问题。

当呼出键盘后,viewWillLayoutSubview viewDidLayoutSubviews 将会被调用。可以看出,每一次布局发生变化时候这两个方法都会被调用。