- 1. ViewController 的生命周期
- 1.1. ViewController 的初始化
- 1.1.1. Storyboard 方式的初始化
- 1.1.2. Code 方式的初始化
- 1.1.3. loadView()
- 1.1.4. viewDidLoad()
- 1.1.5. viewWillAppear(_:)
- 1.1.6. updateViewConstraints
- 1.1.7. viewWillLayoutSubviews()
- 1.1.8. viewDidLayoutSubviews()
- 1.1.9. viewDidAppear(_:)
- 1.1.10. viewWillDisappear(_:)
- 1.1.11. viewDidDisappear(_:)
- 1.1.12. didReceiveMemoryWarning()
- 1.1.13. deinit()
- 1.1.14. 几个例子
- 1.1. ViewController 的初始化
- 2. UIView 的生命周期
ViewController 的生命周期
|
|
ViewController 的初始化
平常我们接触的 ViewController 初始化无非两种方式,一种是通过 Storyboard(Xib类似)初始化,而另外一种就是我们直接通过代码的方式初始化。当然,这两种方式初始化过程是有细微差别的,下面将分别介绍这两种方式的不同初始化过程
Storyboard 方式的初始化
Storyboard 在初始化阶段是由 init(coder:)
-> awakeFromNib()
进行的。
init(coder:)
|
|
awakeFromNib()
|
|
另外,由于是 Archive 并实例化对象,所以 View Controller 在初始化时调用的是 init(coder:)。手动调用 init 则不会从 nib 文件里加载,这个方法在执行 loadNibNamed: 一类的方法时就会被调用
Code 方式的初始化
不同 Storyboard 的是,Code 在初始化阶段是调用 init(nibName:bundle:)
这个方法
init(nibName:bundle:)
|
|
loadView()
View Controller 创建后需要加载 self.view 时会调用这个方法。此方法不应该被直接调用,如果我们的界面是在 Storyboard 中创建的,那我们也不应该覆盖这个方法
当 View Controller 有以下情况时都会在此方法中从 nib 文件加载 View :
- View Controller 是从 storyboard 中实例化的
- 通过 initWithNibName:bundle: 初始化
- 在 App Bundle 中有一个 nib 文件名称和本类名相同
当我们调用 self.view 时,如果 self.view 非 nil,那么会直接调用该对象。如果为 nil,那么会调用 self.loadView() 创建一个 UIView 并将这个对象赋值给 self.view
在这个函数里调用 self.view 属性会造成死循环。因为访问 self.view 时发现该属性为空,会去调用 loadView() 方法,此时会造成死循环
viewDidLoad()
|
|
注意:此时 view 还没有被加入 view hierarchy 中,只是被加载入了内存中。在这里如果执行 self.presentViewController 之类的操作会出错
viewWillAppear(_:)
当 View 将要被添加到 View Hierarchy 中时会调用这个方法,每一次 View 将要显示时都会调用。在这个方法被调用时,也是在显示 View 所需要的动画被配置前
注意:这个时候在做一些和 frame 相关的操作时仍会出错,在这里 View 将要被加入 View Hierarchy,但是仍旧没有被添加进去
updateViewConstraints
updateViewConstraints 是 AutoLayout出现后新增的 api ,苹果官方的解释是该方法是用来添加约束的,重新该方法需要在方法实现的最后调用父类的该方法。并且这两个方法不建议直接调用
这里要注意,如果一个 view 或 controller 是由 interface builder 初始化的,那么这个实例的 updateViewConstraints 或 updateConstraints 方法便会被系统自动调用,起原因应该就是对应的 requiresConstraintBasedLayout 方法返回 true。而纯代码初始化的视图 requiresConstraintBasedLayout 方法默认返回 false。
所以在纯代码自定义一个 view 时,想把约束写在 updateConstraints 方法中,就一定要重写requiresConstraintBasedLayout 方法,返回true。
viewWillLayoutSubviews()
在 ViewController.view 将要布局 Subviews 时调用。当每一次界面的布局发生变化时都会被调用,例如旋转、被标记为需要 layout,在这之后 AutoLayout 会改变布局,在此方法中view的bound和orientation才最终确定,如果没有使用autoresizing mask或autolayout约束,可以在这个方法里布局subview,但是要注意,此方法只要根view的frame发生变化,或者标记为need layout,就会调用此方法,所以可能调多次,不适合做复杂的操作
注意点:init初始化不会触发layoutSubviews
addSubview会触发layoutSubviews
设置view的Frame会触发layoutSubviews,当然前提 是frame的值设置前后发生了变化
滚动一个UIScrollView会触发layoutSubviews
旋转Screen会触发父UIView上的layoutSubviews事件
改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件
viewDidLayoutSubviews()
已经布局完成,也可以做一些操作,已通过 AutoLayout 布局
viewDidAppear(_:)
此时界面已经被显示出来了,做一些操作时可能会让界面变化可见
一个例子
通过 AutoLayout 在界面中添加一个 UITextView,在各个阶段输出它的 Frame 结果如下
viewDidLoad
123frame = (20 40; 560 160);contentSize: {560, 133};contentOffset: {0, 0};viewWillAppear
123frame = (20 40; 560 160);contentOffset: {0, 0};contentSize: {560, 133};viewWillLayoutSubviews
123frame = (20 40; 560 160);contentOffset: {0, 0};contentSize: {560, 133};viewDidLayoutSubviews
123frame = (20 40; 374 296);contentOffset: {0, -15};contentSize: {374, 184}viewDidAppear
123frame = (20 40; 374 296);contentOffset: {0, -15};contentSize: {374, 184};
可以看到,从 viewDidLayoutSubview 开始, UITextView 的 Frame 发生了一次变化, contentSize 与 contentOffset 都发生了变化;因为在 viewWillAppear 及之前, view 还没有被加入层级中,布局还不是最终布局;所以在 viewWillAppear 及以前,计算 UITextView 行高等操作可能会出现问题;当呼出键盘后,viewWillLayoutSubview viewDidLayoutSubviews 将会被调用。可以看出,每一次布局发生变化时候这两个方法都会被调用
viewWillDisappear(_:)
|
|
viewDidDisappear(_:)
|
|
didReceiveMemoryWarning()
|
|
deinit()
|
|
几个例子
屏幕旋转
|
|
- 当 view 转变,会调用
willTransition(to:with:)
方法 - 当屏幕旋转,view 的 bounds 改变,其内部的子控件也需要按照约束调整为新的位置,因此也调用了
viewWillLayoutSubviews()
和viewDidLayoutSubviews()
Present & Dismiss
|
|
- 当在一个控制器内 Present 新的控制器,原先的控制器并不会销毁,但会消失,因此调用了
viewWillDisappear
和viewDidDisappear
方法 - 如果新的控制器 Dismiss,即清除自己,原先的控制器会再一次出现,因此调用了其中的
viewWillAppear
和viewDidAppear
方法
死循环
|
|
- 若
loadView()
没有加载 view,viewDidLoad()
会一直调用loadView()
加载 view,因此构成了死循环,程序即卡死
push到下一个页面时
配合Storyboard方式的方法列表调用顺序
UIView 的生命周期
- 通常,UIView 应尽可能避免重写构造器。
- init(frame:):纯代码(指定构造器);init(coder:):Storyboard(必需可失败构造器)。若需要构造器,需要同时重写这两个构造器:
|
|
- awakeFromNib() 只在使用 Storyboard 的 UIView 中被调用。
- awakeFromNib() 并不是构造器,但它在初始化完成后立即被调用。
- 所有 Storyboard 中继承自 NSObject 的对象发送该消息。但顺序是不确定的,因此不能在这里调用其他任何 Storyboard 中的对象
Xib & Nib
- ib 是 Interface Builder 的缩写,即界面构造器。这里简要说下,Xib 和 Nib 各是什么,有什么区别
- Xib 实际是一个 XML 文件,而 Nib 是二进制文件。当应用编译时,Xib 文件被翻译为 Nib。所以在 Xcode 中,我们可以自己新建 Xib 文件来构造 UI,而当编译时,Xcode 会自动生成相应的 Nib 文件,而不需我们额外关注。关于其详细介绍,您可以参考文末的资料
我们现在新建一个继承UIView的MyView类,并勾选Xib,并将 Utilities 中 Identity inspector 的 Custom Class 改为 MyView12345678910111213141516171819202122import UIKitclass MyView: UIView {override init(frame: CGRect) {super.init(frame: frame)print("init(frame:)")}required init?(coder aDecoder: NSCoder) {super.init(coder: aDecoder)print("init(coder:)")// fatalError("init(coder:) has not been implemented")}override func awakeFromNib() {super.awakeFromNib()print("awakeFromNib()")}}
之后运行即可在屏幕上看到该自定义 UIView,控制台输出:
通过打印的输出,可以看出使用 Interface Builder 载入 View 不会调用 init(frame:)
方法,而是调用了 init(coder:)
。init(coder:)
是 NSCoding 协议中的方法,NSCoding 是负责编码解码,归档处理的协议
init(coder:) 的调用处于 Nib 载入时,而 awakeFromNib() 的调用处于 Nib 载入后。Nib 的载入过程如下:
- Nib 文件内容和引用的资源文件加载到内存;
- 反归档存储于 Nib 文件的图像数据对象并初始化;
- 遵从 NSCoding 的对象(UIView & UIViewController)调用 init(coder:)
- 其他对象调用其他构造器方法
- 建立对象间连接:Outlet & Action
- 实现 awakeFromNib() 的对象调用该方法
需要注意的是,awakeFromNib() 中需要调用父类的该方法以保证父类的进行额外初始化。而在本例中重写的 init(coder:) 目的主要是查看调用顺序,并没有加入特别的操作。因此在实际使用中,如果使用 Interface Builder,可以不重写该方法
Code
|
|
之后在其他UIView或者UIController调用该类的时候
通过纯代码创建自定义 UIView,便只调用 init(frame:)
方法,不涉及 Nib 的方法,因此不会调用 awakeFromNib()
和 init(coder:)
方法。而由于 init(coder:)
为必要构造器,因此重写 init(frame:)
时,必须实现该方法
有时,为了便于从 Interface Builder 和纯代码都能创建自定义 UIView 对象,可以将 init(coder:)
方法改为:
|
|
若保留 fatalError()
,则从 Nib 初始化时会无条件输出语句并停止运行
参考链接
https://juejin.im/entry/57988af4d342d300590d5445
https://juejin.im/post/58c20a761b69e6006bc965ee
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/LoadingResources/CocoaNibs/CocoaNibs.html
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-ID203
https://github.com/kingcos/CS193P_2017
http://amztion.com/2016/12/03/uiviewcontroller-lifecycle/
http://www.infoq.com/cn/articles/ios-app-arch-2-1