APP 生命周期
当我们打开 APP 时,程序一般都是从 main 函数开始运行的,那么我们先来看下 Xcode 自动生成的 main.m 文件:
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
这个默认的 iOS 程序就是从 main 函数开始执行的,但是在 main 函数中我们其实只能看到一个方法,这个方法内部是一个消息循环(相当于一个死循环),因此运行到这个方法 UIApplicationMain 之后程序不会自动退出,而只有当用户手动关闭程序这个循环才结束。我们看下这个方法定义:
int UIApplicationMain(int argc, char * _Nullable *argv, NSString *principalClassName, NSString *delegateClassName);
这个方法有四个参数:
- argc:参数个数,与 main 函数的参数对应。
- argv:参数内容,与 main 函数的参数对应。
- principalClassName:代表 UIApplication 类或其子类。这个参数默认为 nil,则代表 UIApplication 类。UIApplication 是单例模式,一个应用程序只有一个 UIApplication 对象或子对象。
- delegateClassName:代理,默认生成的是 AppDelegate 类,这个类主要用于监听整个应用程序生命周期的各个事件,当UIApplication运行过程中引发了某个事件之后会调用代理中对应的方法。
关于返回值,即便声明了返回值,但该函数也从不会返回。
也就是说当执行 UIApplicationMain 方法后这个方法会根据第三个参数principalClassName
创建对应的 UIApplication 对象,这个对象会根据第四个参数delegateClassName
创建 AppDelegate 并指定此对象为 UIApplication 的代理;同时 UIApplication 会开启一个消息循环不断监听应用程序的各个活动,当应用程序生命周期发生改变 UIApplication 就会调用代理对应的方法。
既然应用程序 UIApplication 是通过代理和外部交互的,那么我们就有必要清楚 AppDelegate 的操作细节,在这个类中定义了生命周期的各个事件的执行方法:
#import "AppDelegate.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSLog(@"程序已经启动");
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application {
NSLog(@"程序将要失去焦点");
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
NSLog(@"程序已经进入后台");
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
NSLog(@"程序将要进入前台");
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
NSLog(@"程序获得焦点");
}
- (void)applicationWillTerminate:(UIApplication *)application {
NSLog(@"程序将要终止");
}
@end
简要说下我们不同的操作,程序运行结果:
-
启动程序
程序已经启动 程序获得焦点
-
按下 home 键
程序将要失去焦点 程序已经进入后台
-
再次打开程序
程序将要进入前台 程序获得焦点
-
下拉状态栏
程序将要失去焦点 程序获得焦点 程序将要失去焦点
-
状态栏收回
程序获得焦点
-
上拉控制中心
程序将要失去焦点
-
收回控制中心
程序获得焦点
-
来电
程序将要失去焦点
-
断电
程序获得焦点
-
双击 Home 并关闭应用
程序将要失去焦点 程序已经进入后台 程序将要终止
通过简单的操作,大家对整个运行周期有了个大概的了解。再附上一张图,让大家有个清晰的认识:
UIViewController 生命周期
总览 UIViewController 生命周期:
下面创建了一个 TestViewController 类,了解下整个过程:
TestViewController.m:
#import "TestViewController.h"
@interface TestViewController ()
@end
@implementation TestViewController
-(instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
NSLog(@"%s",__func__);
return self;
}
-(instancetype)init{
self = [super init];
NSLog(@"%s",__func__);
return self;
}
-(instancetype)initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
NSLog(@"%s",__func__);
return self;
}
-(void)awakeFromNib{
[super awakeFromNib];
NSLog(@"%s",__func__);
}
-(void)loadView{
[super loadView];
NSLog(@"%s",__func__);
}
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"%s",__func__);
}
-(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__);
}
@end
UIViewController 初始化
在 ViewController.m 中:
- (void)viewDidLoad {
[super viewDidLoad];
TestViewController *vc = [[TestViewController alloc]init];
[self.navigationController pushViewController:vc animated:YES];
}
我们在创建 TestViewController 实例时,可以通过以下两种方法:
//第一种
[[TestViewController alloc]initWithNibName:@"ViewController" bundle:nil];
//第二种
[[TestViewController alloc]init];
我们经常使用的是第二种创建方法,其实第二种方法默认实现了第一种的方法,只不过两个参数默认传的是 nil。
当 TestVeiwController 通过 xib 加载的时候,看下 viewDidLoad 之前发生了什么:
-[TestViewController initWithNibName:bundle:]
-[TestViewController init]
-[TestViewController loadView]
-[TestViewController viewDidLoad]
无 xib:
-[TestViewController initWithNibName:bundle:]
-[TestViewController init]
-[TestViewController loadView]
-[TestViewController viewDidLoad]
TestVeiwController 通过 storyboard 加载:
- (void)viewDidLoad {
[super viewDidLoad];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"TestViewController" bundle:nil];
TestViewController *testVC = [storyboard instantiateInitialViewController];
[self.navigationController pushViewController:testVC animated:YES];
}
控制台输出:
-[TestViewController initWithCoder:]
-[TestViewController awakeFromNib]
-[TestViewController loadView]
-[TestViewController viewDidLoad]
我们可以看到通过 storyboard 实例化与 init
实例化在 loadView
方法调用之前走的是不同的方法。我们看下这几个方法的不同:
initWithNibName:bundle:
此方法发生在 nib 加载之前。
调用此方法进行 Controller 初始化,与 nib 加载无关。nib 的加载是懒加载,当 Controller 需要加载其视图时,才会加载此方法中指定的 nib。
可以看出该方法初始化的 Controller 不是从 nib 创建的。
initWithCoder
此方法发生在 nib 加载期间。
所有 archived 对象的初始化使用此方法。nib 中存储的对象就是 archived 对象,所以此方法是 nib 加载对象时使用的初始化方法。
当从 nib 创建 UIViewController 时使用此方法。
awakeFromNib
此方法发生在 nib 中所有对象都已完全加载完之后。
如果 initWithCoder
是 unarchiving 开始,那此方法就是结束。
loadView 与 veiwDidLoad
在此方法中创建视图。
我们可以通过下图来理解它的逻辑:
每次访问 view 时,就会调用 self.view 的 get 方法,在 get 方法中判断self.view==nil
,不为 nil 就直接返回 view,等于 nil 就去调用 loadView 方法。loadView 方法会去判断有无指定 storyBord/Xib 文件,如果有就去加载 storyBord/Xib 描述的控制器 view,如果没有则系统默认创建一个空的 view,赋给 self.view。loadView 方法有可能被多次调用(每当访问 self.view 并且为 nil 时就会调用一次);
系统会自动为我们加载 view,我们完全没必要手动创建 view。
viewWillAppear
视图将要被展示的时候调用。
其调用的时机与视图所在层次有关。例如我们常用的 push 与 present 操作改变了当前视图层次,都会触发此方法。
1、那么 UIAlertController 也是 present 操作怎么没有触发呢?
因为 UIAlertController 在另一个 window 上,view 在自己所在的 window 中层次并没有改变,所以不会触发,同理在锁屏以及进入后台时也不会触发。
2、如果控制器 B 被展示在另一个控制器 A 的 popover 中,那么被展示的控制器 B 在消失后,控制器 A 并不会调用此方法。
官方原文:
If a view controller is presented by a view controller inside of a popover, this method is not invoked on the presenting view controller after the presented controller is dismissed.
例如我们使用的addSubview
方法,如下:
AViewController.m 中:
BViewController *B = [[BViewController alloc]init];
[self addChildViewController:B];
[self.view addSubview:B.view];
当我们将 BViewController 从 AViewController 中移除后,并不会触发 AViewController 的 viewWillAppear
方法。
viewDidAppear
视图渲染完成后调用,与viewWillAppear
配套使用。
viewWillLayoutSubviews 与 viewDidLayoutSubviews
这两个方法发生在 viewWillAppear
与 viewDidAppear
之间。
-
viewWillLayoutSubviews
控制器将要布局 view 的子控件时调用,默认实现为空。此时子控件的大小还没有设置好。
-
viewDidLayoutSubviews
控制器已经布局 view 的子控件时调用,默认实现为空。此时子控件的大小才被设置好,这里才是获取子视图大小的正确位置。
viewWillDisappear 与 viewDidDisappear
viewWillDisappear
与viewDidDisappear
配套使用。
-
viewWillDisappear
视图将要消失时调用
-
viewDidDisappear
视图完全消失后调用
两个方法的调用时机同viewWillAppear
和viewDidAppear
道理相同。
didReceiveMemoryWarning 与 viewDidUnload
这两个方法是收到内存警告时调用的。
- viewDidUnload
在 iOS5 以及之前使用的方法,iOS6 及之后已经废弃。在收到内存警告时,在此方法中将 view 置为 nil;
- didReceiveMemoryWarning
收到内存警告时,系统自动调用此方法,回收占用大量内存的视图数据。我们一般不需要在这里做额外的操作。如果要自己处理一些额外内存,重写时需要调用父类方法,即[super didReceiveMemoryWarning]
。
dealloc
UIViewController 释放时调用此方法。UIViewController 的生命周期到此结束。
当我们重写此方法时,ARC 环境下不需要调用父类方法,MRC 环境下需要调用父类方法,即[super dealloc]
。
小结
本篇主要介绍了 APP 的生命周期,以及 UIViewController 的生命周期,对我们程序开发的过程有了更清晰的认识。
参考资料: