一、
ViewController
的职责
对内管理与之关联的 View ,对外跟其他 ViewController 通信和协调。对于与之关联的 View , ViewController 总是在需要的时候才加载视图,并在不需要的时候卸载视图,所以也同时担当了管理应用资源的责任
二、 ViewController 的生命周期
View 是指 Controller 的 View 。它作为 Controler 的属性,生命周期在 Controller 的生命周期内。就是说你的 Controller 不能在 view 释放前就释放了。
viewController
的生命周期图
init -> loadView ->viewDidLoad-> viewWillAppear->viewDidAppear->viewWillDisappear->viewDidDisappear->viewDidUnload->dealloc
需要说明的是:当你
alloc
并
init
了一个
ViewController
时,这个
ViewController
应该是还没有创建
view
的。
ViewController
的
view
是使用了
lazyInit
方式创建,就是说你调用的
view
属性的
getter
:[
self view
]。在
getter
里会先判断
view
是否创建,如果没有创建,那么会调用
loadView
来创建
view
。
loadView
完成时会继续调用
viewDidLoad
。
loadView
和
viewDidLoad
的一个区别就是:
loadView
时还没有
view
。而
viewDidLoad
时
view
以及创建好了。
三、 view 的加载过程
跟随如下文字理解 viewController 对 view 加载过程:
1 先判断子类是否重写了 loadView ,如果有直接调用。之后调 viewDidLoad 完成 View 的加载。
2 如果是外部通过调用 initWithNibName:bundle 指定 nib 文件名的话, ViewController 记载此 nib 来创建 View 。
3 如果 initWithNibName:bundle 的 name 参数为 nil ,则 ViewController 会通过以下两个步骤找到与其关联的 nib 。
A 如果类名包含 Controller ,例如 ViewController 的类名是 MyViewController ,则查找是否存在 MyView.nib ;
B 找跟 ViewController 类名一样的文件,例如 MyViewController ,则查找是否存在 MyViewController.nib 。
4
如果子类没有重写的
loadView
,则
ViewController
会从
StroyBoards
中找或者调用其默认的
loadView
,默认的
loadView
返回一个空白的
UIView
对象。
注意第一步, ViewController 是判断子类是否重写了 loadView ,而不是判断调用子类的 loadView 之后 ViewController 的 View 是否为空。就是说,如果子类重写了 loadView 的话,不管子类在 loadView 里面能否获取到 View , ViewController 都会直接调 viewDidLoad 完成 View 的加载。
跟随以下文字理解卸载过程:
1 系统发出警告或者 ViewController 本身调用导致 didReceiveMemoryWarning 被调用
2 调用 viewWillUnload 之后释放 View
3 调用 viewDidUnload
四、模拟器的调用顺序
我构架了这样一个环境,在该环境中有两个 viewController ,姑且命名为 A 和 B , tag 分别为 1 和 2 , A 控制程序启动的时候即加载的界面,在 A 中放一个按钮,按下后会通过 segue 来调用到界面 B ; B 中页放一个按钮,通过执行
[self dismissModalViewControll
erAnimated:YES];
来返回界面 A
然后检测所有的函数调用,依次如下
加载 A 的时候依次调用
1 initWithCoder
1 loadView // 如果说你进行了重写,会在这里调用,这一步可以参考下文
1 viewDidLoad
1 viewWillAppear
1 viewWillLayoutSubviews
1 viewDidLayoutSubviews
1 viewDidAppear
切换至 B 的时候依次调用
2 initWithCoder
//
先将
2
初始化
1 prepareForSegue //
调用
1
的准备过度的函数,所以在该函数中可以对界面
B
的一些相关属性进行赋值
2 loadView
//
如果这里进行了重写
2 viewDidLoad
//2
界面加载
1 viewWillDisappear
2 viewWillAppear
2 viewWillLayoutSubviews
2 viewDidLayoutSubviews
2 viewDidAppear
1 viewDidDisappear
从 B 切换回 A 的时候依次调用
2 viewWillDisappear
1 viewWillAppear
1 viewDidAppear
2 viewDidDisappear
2 dealloc
顺序总结下来加载依次为:加载 - 显示 - 布局
完成顺序依次为:完成布局 - 完成显示 -
完成加载
小注: -(void)loadView; 函数如果重写,下面是一个可能的 demo
-(void)loadView
{
CGRect applicationFrame = [[UIScreenmainScreen] applicationFrame];
UIView *contentView = [[UIViewalloc] initWithFrame:applicationFrame];
contentView.backgroundColor = [UIColordarkGrayColor];
self.view = contentView;
UILabel *lab = [[UILabelalloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
lab.text = @"HelloWorld";
[self.viewaddSubview:lab];
}
loadView 虽然返回值为空,但必须在函数体内对 self.view 进行赋值,否则会在建立该界面的时候收到如下的 log 信息:
Application windows are expected to have a root view controller at the end of application launch
具体执行顺序为:代码执行了 initWithCoder 之后直接调用了三次 loadView 函数,并且没有调用其它函数(包括 viewDidLoad 、 viewWillDisappear 、 viewWillLayoutSubviews )
疑问:
暂不清楚为什么会调用三次,我的猜测是:上述三个函数分别检测了一遍 view 是否存在,发现不存在,所以各自调用了一遍 viewLoad ,最后发现依然不存在,所以上述三个函数分别返回了失败,加载完成
但矛盾的地方是:为什么上述三个函数本身没有执行到?底层到底做了什么?
五、 view 和 ViewController 的创建阶段,关于什么时候应该干什么
1 、 init
Allocating critical data structures required by your view controller
不要出现创建 view 的代码。良好的设计,在 init 里应该只有相关数据的初始化,而且这些数据都是比较关键的数据。 init 里不要掉 self.view ,否则会导致 viewcontroller 创建 view 。(因为 view 是 lazyinit 的)。
2 、 loadView
Creating your view objects
只初始化 view ,一般用于创建比较关键的 view 如 tableViewController 的 tabView , UINavigationController 的 navgationBar ,不可掉用 view 的 getter (在掉 super loadView 前),最好也不要初始化一些非关键的 view 。如果你是从 nib 文件中创建的 viewController 在这里一定要首先调用 super 的 loadView 方法,但建议不要重载这个方法。
3 、 viewDidLoad
Allocating or loading data to be displayed in your view
这时候 view 已经有了,最适合创建一些附加的 view 和控件了。有一点需要注意的是, viewDidLoad 会调用多次( viewcontroller 可能多次载入 view ,参见图 2 )。
4 、 viewWillAppear 这个一般在 view 被添加到 superview 之前,切换动画之前调用。在这里可以进行一些显示前的处理。比如键盘弹出,一些特殊的过程动画(比如状态条和 navigationbar 颜色)。
5 、 viewDidAppear 一般用于显示后,在切换动画后,如果有需要的操作,可以在这里加入相关代码。
6 、 viewDidUnload
Releasing references to view objects
Releasing data that is not needed when your view is not displayed
这时候 viewController 的 view 已经是 nil 了。由于这一般发生在内存警告时,所以在这里你应该将那些不在显示的 view 释放了。比如你在 viewcontroller 的 view 上加了一个 label ,而且这个 label 是 viewcontroller 的属性,那么你要把这个属性设置成 nil ,以免占用不必要的内存,而这个 label 在 viewDidLoad 时会重新创建。
7 、 dealloc
Releasing critical data structures required by your view controller
六、几点备注:
1 、按结构可以对 iOS 的所有 ViewController 分成两类:
1 )、主要用于展示内容的 ViewController ,这种 ViewController 主要用于为用户展示内容,并与用户交互,如 UITableViewController , UIViewController 。
2 )、用于控制和显示其他 ViewController 的 ViewController 。这种 ViewController 一般都是一个 ViewController 的容器。如 UINavigationController , UITabbarController 。它们都有一个属性: viewControllers 。其中 UINavigationController 表示一种 Stack 式结构, push 一个 ViewController 或 pop 一次,因此后一个 ViewController 一般会依赖前一个 ViewController 。而 UITabbarController 表示一个 Array 结构,各个 ViewController 是并列的。
第一种 ViewController 会经常被继承,用来显示不同的数据给用户。而第二种很少被继承,除非你真的需要自定义它。
2 、当 view 被添加其他 view 中之前时,会调用 viewWillAppear ,而之后会调用 viewDidAppear 。
当 view 从其他 view 中移出之前时,会调用 viewWillDisAppear ,而之后会调用 viewDidDisappear 。
当 view 不在使用,而且是 disappeared ,受到内存警告时,那么 viewController 会将 view 释放并将其指向 nil 。
3 、由于 Controller 加载 View 时,会自动将一些 View 对象指向其对应的 IBOutlet 变量。
所以当 view 被卸载时我们必须在 viewDidUnload 将这些变量 release 掉, ViewController 不会自动做这件事。
对内管理与之关联的 View ,对外跟其他 ViewController 通信和协调。对于与之关联的 View , ViewController 总是在需要的时候才加载视图,并在不需要的时候卸载视图,所以也同时担当了管理应用资源的责任
二、 ViewController 的生命周期
View 是指 Controller 的 View 。它作为 Controler 的属性,生命周期在 Controller 的生命周期内。就是说你的 Controller 不能在 view 释放前就释放了。
init -> loadView ->viewDidLoad-> viewWillAppear->viewDidAppear->viewWillDisappear->viewDidDisappear->viewDidUnload->dealloc
三、 view 的加载过程
跟随如下文字理解 viewController 对 view 加载过程:
1 先判断子类是否重写了 loadView ,如果有直接调用。之后调 viewDidLoad 完成 View 的加载。
2 如果是外部通过调用 initWithNibName:bundle 指定 nib 文件名的话, ViewController 记载此 nib 来创建 View 。
3 如果 initWithNibName:bundle 的 name 参数为 nil ,则 ViewController 会通过以下两个步骤找到与其关联的 nib 。
A 如果类名包含 Controller ,例如 ViewController 的类名是 MyViewController ,则查找是否存在 MyView.nib ;
B 找跟 ViewController 类名一样的文件,例如 MyViewController ,则查找是否存在 MyViewController.nib 。
4
注意第一步, ViewController 是判断子类是否重写了 loadView ,而不是判断调用子类的 loadView 之后 ViewController 的 View 是否为空。就是说,如果子类重写了 loadView 的话,不管子类在 loadView 里面能否获取到 View , ViewController 都会直接调 viewDidLoad 完成 View 的加载。
跟随以下文字理解卸载过程:
1 系统发出警告或者 ViewController 本身调用导致 didReceiveMemoryWarning 被调用
2 调用 viewWillUnload 之后释放 View
3 调用 viewDidUnload
四、模拟器的调用顺序
我构架了这样一个环境,在该环境中有两个 viewController ,姑且命名为 A 和 B , tag 分别为 1 和 2 , A 控制程序启动的时候即加载的界面,在 A 中放一个按钮,按下后会通过 segue 来调用到界面 B ; B 中页放一个按钮,通过执行
[self dismissModalViewControll
来返回界面 A
然后检测所有的函数调用,依次如下
加载 A 的时候依次调用
1 initWithCoder
1 loadView // 如果说你进行了重写,会在这里调用,这一步可以参考下文
1 viewDidLoad
1 viewWillAppear
1 viewWillLayoutSubviews
1 viewDidLayoutSubviews
1 viewDidAppear
切换至 B 的时候依次调用
2 initWithCoder
1 prepareForSegue
2 loadView
2 viewDidLoad
1 viewWillDisappear
2 viewWillAppear
2 viewWillLayoutSubviews
2 viewDidLayoutSubviews
2 viewDidAppear
1 viewDidDisappear
从 B 切换回 A 的时候依次调用
2 viewWillDisappear
1 viewWillAppear
1 viewDidAppear
2 viewDidDisappear
2 dealloc
顺序总结下来加载依次为:加载 - 显示 - 布局
完成顺序依次为:完成布局 - 完成显示
小注: -(void)loadView; 函数如果重写,下面是一个可能的 demo
-(void)loadView
{
}
loadView 虽然返回值为空,但必须在函数体内对 self.view 进行赋值,否则会在建立该界面的时候收到如下的 log 信息:
Application windows are expected to have a root view controller at the end of application launch
具体执行顺序为:代码执行了 initWithCoder 之后直接调用了三次 loadView 函数,并且没有调用其它函数(包括 viewDidLoad 、 viewWillDisappear 、 viewWillLayoutSubviews )
疑问:
暂不清楚为什么会调用三次,我的猜测是:上述三个函数分别检测了一遍 view 是否存在,发现不存在,所以各自调用了一遍 viewLoad ,最后发现依然不存在,所以上述三个函数分别返回了失败,加载完成
但矛盾的地方是:为什么上述三个函数本身没有执行到?底层到底做了什么?
五、 view 和 ViewController 的创建阶段,关于什么时候应该干什么
1 、 init
Allocating critical data structures required by your view controller
不要出现创建 view 的代码。良好的设计,在 init 里应该只有相关数据的初始化,而且这些数据都是比较关键的数据。 init 里不要掉 self.view ,否则会导致 viewcontroller 创建 view 。(因为 view 是 lazyinit 的)。
2 、 loadView
Creating your view objects
只初始化 view ,一般用于创建比较关键的 view 如 tableViewController 的 tabView , UINavigationController 的 navgationBar ,不可掉用 view 的 getter (在掉 super loadView 前),最好也不要初始化一些非关键的 view 。如果你是从 nib 文件中创建的 viewController 在这里一定要首先调用 super 的 loadView 方法,但建议不要重载这个方法。
3 、 viewDidLoad
Allocating or loading data to be displayed in your view
这时候 view 已经有了,最适合创建一些附加的 view 和控件了。有一点需要注意的是, viewDidLoad 会调用多次( viewcontroller 可能多次载入 view ,参见图 2 )。
4 、 viewWillAppear 这个一般在 view 被添加到 superview 之前,切换动画之前调用。在这里可以进行一些显示前的处理。比如键盘弹出,一些特殊的过程动画(比如状态条和 navigationbar 颜色)。
5 、 viewDidAppear 一般用于显示后,在切换动画后,如果有需要的操作,可以在这里加入相关代码。
6 、 viewDidUnload
Releasing references to view objects
Releasing data that is not needed when your view is not displayed
这时候 viewController 的 view 已经是 nil 了。由于这一般发生在内存警告时,所以在这里你应该将那些不在显示的 view 释放了。比如你在 viewcontroller 的 view 上加了一个 label ,而且这个 label 是 viewcontroller 的属性,那么你要把这个属性设置成 nil ,以免占用不必要的内存,而这个 label 在 viewDidLoad 时会重新创建。
7 、 dealloc
Releasing critical data structures required by your view controller
六、几点备注:
1 、按结构可以对 iOS 的所有 ViewController 分成两类:
1 )、主要用于展示内容的 ViewController ,这种 ViewController 主要用于为用户展示内容,并与用户交互,如 UITableViewController , UIViewController 。
2 )、用于控制和显示其他 ViewController 的 ViewController 。这种 ViewController 一般都是一个 ViewController 的容器。如 UINavigationController , UITabbarController 。它们都有一个属性: viewControllers 。其中 UINavigationController 表示一种 Stack 式结构, push 一个 ViewController 或 pop 一次,因此后一个 ViewController 一般会依赖前一个 ViewController 。而 UITabbarController 表示一个 Array 结构,各个 ViewController 是并列的。
第一种 ViewController 会经常被继承,用来显示不同的数据给用户。而第二种很少被继承,除非你真的需要自定义它。
2 、当 view 被添加其他 view 中之前时,会调用 viewWillAppear ,而之后会调用 viewDidAppear 。
当 view 从其他 view 中移出之前时,会调用 viewWillDisAppear ,而之后会调用 viewDidDisappear 。
当 view 不在使用,而且是 disappeared ,受到内存警告时,那么 viewController 会将 view 释放并将其指向 nil 。
3 、由于 Controller 加载 View 时,会自动将一些 View 对象指向其对应的 IBOutlet 变量。
所以当 view 被卸载时我们必须在 viewDidUnload 将这些变量 release 掉, ViewController 不会自动做这件事。
具体做法是将变量设置为空,(注意和dealloc中将变量release的区别)注意此时Controller的view属性是空的。