UIViewController的生命周期
文章目录
- UIViewController的生命周期
- 前言
- UIViewController的一个结构
- UIViewController的函数的执行顺序
- 运行代码
- viewWillAppear && viewDidAppear
- 多个视图控制器跳转时的生命周期
- push
- present
- 小结
前言
之前对于有关于UIViewControlller的理解比较浅显,仅仅只知道他是用来加载视图的,后面在有关天气预报的内容中了解了有关视图控制器生命周期的内容。
UIViewController的一个结构
UIViewController这个视图控制器
UIViewController的函数的执行顺序
这里先给出一个图片来展示一下流程,然后我们在通过讲解一下相关的内容,这里我们尝试在打印所有的内容。
这里笔者想通过两个视图控制之间相互切换来实现一个展示每一个视图控制器生命周期的效果,这里我们先讨论有关于
loadView
这个函数开始的一些执行过程。
这里我们先要重写有关ViewController的生命周期中所有函数,让他先可以打印自己的函数名。
//
// FirsttViewController.m
// ViewController的生命周期
//
// Created by nanxun on 2024/9/9.
//
#import "FirsttViewController.h"
#import "TestViewController.h"
@interface FirsttViewController ()
@end
@implementation FirsttViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = UIColor.whiteColor;
UIView* myView = [[UIView alloc] initWithFrame:CGRectMake(80, 80, 80, 80)];
myView.backgroundColor = UIColor.redColor;
[self.view addSubview:myView];
UIButton* btn = [UIButton buttonWithType:UIButtonTypeCustom];
btn.frame = CGRectMake(200, 200, 50, 50);
btn.backgroundColor = UIColor.redColor;
[self.view addSubview:btn];
[btn addTarget:self action:@selector(press) forControlEvents:UIControlEventTouchUpInside];
NSLog(@"%s", __func__);
// Do any additional setup after loading the view.
}
-(void)loadView {
[super loadView];//注意这里重写子类方法的时候记得要先调用父类方法
NSLog(@"%s", __func__);
}
-(void)press {
TestViewController* vc =[[TestViewController alloc] init];
[self.navigationController pushViewController:vc animated:YES];
}
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(@"%s", __func__);
}
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(@"%s", __func__);
}
-(void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
NSLog(@"%s", __func__);
}
-(void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
NSLog(@"%s", __func__);
}
-(void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
NSLog(@"%s", __func__);
}
-(void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
NSLog(@"%s", __func__);
}
- (void)dealloc {
NSLog(@"%s", __func__);
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
@end
运行代码
这里我们通过打断点的方式给每一个ViewController的方法都打上断点,然后我们先加载第一个视图控制器
这里可以看到我们的代码是这样一个执行流程:先执行loadView
这一步是将view载入到内存中,然后我们在viewDidLoad
这个方法中把相关的控件加载到我们的view上,再执行ViewWillAppear
这个方法,后面会调用ViewWillLayoutSubview
这个方法,然后就会把视图布局好,然后执行ViewDidLayoutSubview
这个方法然后我们就会执行ViewDidAppear
这个方法来完成我们的所有视图的加载。
这时候我们通过点击我们的按钮然后我们这里可以看到两个视图控制器,从前一个视图控制器转移到后面的视图控制器的过程并不是前一个视图控制器直接执行viewWillDisAppear
而是后一个视图控制器先执行viewDidLoad
这个方法加载好控件之后前一个视图控制开始执行有关消失的方法,后一个视图控制器开始执行有关视图出现的函数,这样个流程才实现一个视图控制器的完整的生命周期,下面我给出打印的内容。
后面的视图控制器出现在屏幕上,和前一个视图控制器的view消失是一个交替的过程,这里可以看到我们的第一个视图控制器并没有被释放,没有执行有关视图控制器销毁的函数,但是当我们从后面的一个视图控制器跳转到前一个视图控制器的时候,后面的视图会执行一个dealloc
的销毁函数,这里指的是视图控制器的销毁。
这里笔者简单讲述分析一下每一个视图控制器调用的函数的相关作用:(引用自UIViewController的生命周期)
- 1、
initWithCoder:
或initWithNibName:Bundle
首先从归档文件中加载UIViewController
对象。即使是纯代码,也会把nil作为参数传给后者。- 2、
awakeFromNib
作为第一个方法的助手,方法处理一些额外的设置- 3、
loadView
创建或加载一个view并把它赋值给UIViewController
的view
属性- 4、
viewDidLoad
此时整个视图层次(view hierarchy
)已经放到内存中,可以移除一些视图,修改约束,加载数据等- 5、
viewWillAppear
视图加载完成,并即将显示在屏幕上。还没设置动画,可以改变当前屏幕方向或状态栏的风格等- 6、
viewWillLayoutSubviews
即将开始子视图位置布局- 7、
viewDidLayoutSubviews
用于通知视图的位置布局已经完成- 8、
viewDidAppear
视图已经展示在屏幕上,可以对视图做一些关于展示效果方面的修改。- 9、
viewWillDisappear
视图即将消失- 10、
viewDidDisappear
视图已经消失- 11、
dealloc
视图销毁的时候调用
笔者这里补充一下有关于前三个函数内容理解:
-
这里可以看到我么的第一步创建方式如果是代码的方式他会执行
initWithNibName:Bundle
这个方法,我们因为是通过纯代码方式创建的,会将nil作为一个参数传到后者。我们的另一种则是通过故事板来创建。 -
当
awakeFromNib
方法被调用时,所有视图的outlet
和action
已经连接,但还没有被确定,这个方法可以算作适合视图控制器的实例化配合一起使用的,因为有些需要根据用户喜好来进行设置的内容,无法存在storyBoard
或xib
中,所以可以在awakeFromNib
方法中被加载进来。 -
loadView
这个方法中,要正式加载View了。首先我们得知道,控制器 view 是通过懒加载的方式进行加载的,即用到的时候再加载。永远不要主动调用这个方法。当我们用到控制器 view 时,就会调用控制器 view 的 get 方法,在 get 方法内部,首先判断 view 是否已经创建,如果已存在,则直接返回存在的 view,如果不存在,则调用控制器的 loadView 方法,在控制器没有被销毁的情况下,loadView 也可能会被执行多次。- 这里提到了如果不存在view的话他会执行多次
loadView
这里可能会出现一个死循环,也就是说我们在重写的loadView
方法中没有创建view这里就会出现一个死循环的问题 - 对于是否要调用
super loadView
这个语句的话,并把子类的 view 赋给 view 属性 (property) (你 create 的 view 必须是唯一的实例,并且不被其他任何 controller 共享)。 **如果你要进行进一步初始化你的 views,你应该在 viewDidLoad 函数中去做。**在实际上我们如果想重写这个方法的时候也是要设置一个不同的子类view,而如果调用的是super loadView
这个语句的话,他只会返回一个空白的View,在开发的角度来说没有什么意义,笔者这里仅仅只是为了展示UIViewController的一个生命周期才调用上述的这个方法。
这里有图可以很好的展示相关的内容:
- 这里提到了如果不存在view的话他会执行多次
viewWillAppear && viewDidAppear
- viewWillAppear:在系统载入视图的时候,会调用这个方法,我们可以在这个方法中对将要显示的视图再进一步的设置,同时调用数据要更新的时候,都在这个方法里面实现。
- viewDidAppear: 在view被添加到视图层级中以及多视图,上下级视图切换时调用这个方法,在这里可以对正在显示的视图做进一步的设置。
视图层次(view hierachy)因为每个视图都有自己的子视图,这个视图层次其实也可以理解为一颗树状的数据结构。而树的根节点,也就是
根视图(root view)
,在UIViewController
中以view
属性。它可以看做是其他所有子视图的容器,也就是根节点。
多个视图控制器跳转时的生命周期
push
当我们点击 push 的时候首先会加载下一个界面然后才会调用界面的消失方法。
- init:ViewController2
- loadView:ViewController2
- viewDidLoad:ViewController2
- viewWillDisappear:ViewController1 将要消失
- viewWillAppear:ViewController2 将要出现
- viewWillLayoutSubviews ViewController2
- viewDidLayoutSubviews ViewController2
- viewWillLayoutSubviews:ViewController1
- viewDidLayoutSubviews:ViewController1
- viewDidDisappear:ViewController1 完全消失
- viewDidAppear:ViewController2 完全出现
上面的图片也展示出了这个过程。
push会调用我们的viewDidDisappear
方法
present
但是present方法和push调用的视图控制器的流程是不一样的:
这里发现我们的present方法并不会让我们的前一个视图控制器调用viewWillAppear
和viewDidAppear
这两个方法,同时也不会调用有关于viewDidDisappear
和viewWillDisAppear
小结
笔者对于UIViewController
的生命周期有了一点简单的认识,后面会讲一下这里push和present两个方法具体区别。
参考博客
UIViewController的生命周期