iOS 生命周期

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 并关闭应用

    程序将要失去焦点
    程序已经进入后台
    程序将要终止
    

通过简单的操作,大家对整个运行周期有了个大概的了解。再附上一张图,让大家有个清晰的认识:

image

UIViewController 生命周期

总览 UIViewController 生命周期:

image

下面创建了一个 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

在此方法中创建视图。

我们可以通过下图来理解它的逻辑:

image

每次访问 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

这两个方法发生在 viewWillAppearviewDidAppear 之间。

  • viewWillLayoutSubviews

    控制器将要布局 view 的子控件时调用,默认实现为空。此时子控件的大小还没有设置好。

  • viewDidLayoutSubviews

    控制器已经布局 view 的子控件时调用,默认实现为空。此时子控件的大小才被设置好,这里才是获取子视图大小的正确位置。

viewWillDisappear 与 viewDidDisappear

viewWillDisappearviewDidDisappear配套使用。

  • viewWillDisappear

    视图将要消失时调用

  • viewDidDisappear

    视图完全消失后调用

两个方法的调用时机同viewWillAppearviewDidAppear道理相同。

didReceiveMemoryWarning 与 viewDidUnload

这两个方法是收到内存警告时调用的。

  • viewDidUnload

在 iOS5 以及之前使用的方法,iOS6 及之后已经废弃。在收到内存警告时,在此方法中将 view 置为 nil;

  • didReceiveMemoryWarning

收到内存警告时,系统自动调用此方法,回收占用大量内存的视图数据。我们一般不需要在这里做额外的操作。如果要自己处理一些额外内存,重写时需要调用父类方法,即[super didReceiveMemoryWarning]

dealloc

UIViewController 释放时调用此方法。UIViewController 的生命周期到此结束。

当我们重写此方法时,ARC 环境下不需要调用父类方法,MRC 环境下需要调用父类方法,即[super dealloc]

小结

本篇主要介绍了 APP 的生命周期,以及 UIViewController 的生命周期,对我们程序开发的过程有了更清晰的认识。

参考资料:

https://www.cnblogs.com/kenshincui/p/3890880.html

https://www.quora.com/Cocoa-API-What-is-the-difference-between-initWithCoder-initWithNibName-and-awakeFromNib-1

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • 参考自滚滚猫的《iOS APP生命周期 和 UIViewController的生命周期》,沧州宁少的《iOS Ap...
    小暖风阅读 2,807评论 0 6
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,084评论 1 32
  • load初始化方法<加载到内存就会执行,不需要触发,且只会调用一次> + (void)load 只要加载内存中就会...
    flowerflower阅读 725评论 1 2
  • ViewController生命周期 按照执行顺序排列: initWithCoder:通过nib文件初始化时触发。...
    隐身人阅读 684评论 3 7
  • 译者注:本文是对 Apple 官方文档的翻译,原文地址为:https://developer.apple.com/...
    ampire_dan阅读 7,393评论 0 13