APP 生命周期
當(dāng)我們打開 APP 時(shí)徒坡,程序一般都是從 main 函數(shù)開始運(yùn)行的,那么我們先來(lái)看下 Xcode 自動(dòng)生成的 main.m 文件:
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
這個(gè)默認(rèn)的 iOS 程序就是從 main 函數(shù)開始執(zhí)行的腐宋,但是在 main 函數(shù)中我們其實(shí)只能看到一個(gè)方法,這個(gè)方法內(nèi)部是一個(gè)消息循環(huán)(相當(dāng)于一個(gè)死循環(huán))檀轨,因此運(yùn)行到這個(gè)方法 UIApplicationMain 之后程序不會(huì)自動(dòng)退出脏款,而只有當(dāng)用戶手動(dòng)關(guān)閉程序這個(gè)循環(huán)才結(jié)束。我們看下這個(gè)方法定義:
int UIApplicationMain(int argc, char * _Nullable *argv, NSString *principalClassName, NSString *delegateClassName);
這個(gè)方法有四個(gè)參數(shù):
- argc:參數(shù)個(gè)數(shù)裤园,與 main 函數(shù)的參數(shù)對(duì)應(yīng)。
- argv:參數(shù)內(nèi)容剂府,與 main 函數(shù)的參數(shù)對(duì)應(yīng)拧揽。
- principalClassName:代表 UIApplication 類或其子類。這個(gè)參數(shù)默認(rèn)為 nil腺占,則代表 UIApplication 類淤袜。UIApplication 是單例模式,一個(gè)應(yīng)用程序只有一個(gè) UIApplication 對(duì)象或子對(duì)象衰伯。
- delegateClassName:代理铡羡,默認(rèn)生成的是 AppDelegate 類,這個(gè)類主要用于監(jiān)聽整個(gè)應(yīng)用程序生命周期的各個(gè)事件意鲸,當(dāng)UIApplication運(yùn)行過程中引發(fā)了某個(gè)事件之后會(huì)調(diào)用代理中對(duì)應(yīng)的方法烦周。
關(guān)于返回值尽爆,即便聲明了返回值,但該函數(shù)也從不會(huì)返回读慎。
也就是說(shuō)當(dāng)執(zhí)行 UIApplicationMain 方法后這個(gè)方法會(huì)根據(jù)第三個(gè)參數(shù)principalClassName
創(chuàng)建對(duì)應(yīng)的 UIApplication 對(duì)象漱贱,這個(gè)對(duì)象會(huì)根據(jù)第四個(gè)參數(shù)delegateClassName
創(chuàng)建 AppDelegate 并指定此對(duì)象為 UIApplication 的代理;同時(shí) UIApplication 會(huì)開啟一個(gè)消息循環(huán)不斷監(jiān)聽?wèi)?yīng)用程序的各個(gè)活動(dòng)夭委,當(dāng)應(yīng)用程序生命周期發(fā)生改變 UIApplication 就會(huì)調(diào)用代理對(duì)應(yīng)的方法幅狮。
既然應(yīng)用程序 UIApplication 是通過代理和外部交互的,那么我們就有必要清楚 AppDelegate 的操作細(xì)節(jié)株灸,在這個(gè)類中定義了生命周期的各個(gè)事件的執(zhí)行方法:
#import "AppDelegate.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSLog(@"程序已經(jīng)啟動(dòng)");
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application {
NSLog(@"程序?qū)⒁ソ裹c(diǎn)");
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
NSLog(@"程序已經(jīng)進(jìn)入后臺(tái)");
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
NSLog(@"程序?qū)⒁M(jìn)入前臺(tái)");
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
NSLog(@"程序獲得焦點(diǎn)");
}
- (void)applicationWillTerminate:(UIApplication *)application {
NSLog(@"程序?qū)⒁K止");
}
@end
簡(jiǎn)要說(shuō)下我們不同的操作崇摄,程序運(yùn)行結(jié)果:
-
啟動(dòng)程序
程序已經(jīng)啟動(dòng) 程序獲得焦點(diǎn)
-
按下 home 鍵
程序?qū)⒁ソ裹c(diǎn) 程序已經(jīng)進(jìn)入后臺(tái)
-
再次打開程序
程序?qū)⒁M(jìn)入前臺(tái) 程序獲得焦點(diǎn)
-
下拉狀態(tài)欄
程序?qū)⒁ソ裹c(diǎn) 程序獲得焦點(diǎn) 程序?qū)⒁ソ裹c(diǎn)
-
狀態(tài)欄收回
程序獲得焦點(diǎn)
-
上拉控制中心
程序?qū)⒁ソ裹c(diǎn)
-
收回控制中心
程序獲得焦點(diǎn)
-
來(lái)電
程序?qū)⒁ソ裹c(diǎn)
-
斷電
程序獲得焦點(diǎn)
-
雙擊 Home 并關(guān)閉應(yīng)用
程序?qū)⒁ソ裹c(diǎn) 程序已經(jīng)進(jìn)入后臺(tái) 程序?qū)⒁K止
通過簡(jiǎn)單的操作,大家對(duì)整個(gè)運(yùn)行周期有了個(gè)大概的了解慌烧。再附上一張圖逐抑,讓大家有個(gè)清晰的認(rèn)識(shí):
UIViewController 生命周期
總覽 UIViewController 生命周期:
下面創(chuàng)建了一個(gè) TestViewController 類,了解下整個(gè)過程:
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];
}
我們?cè)趧?chuàng)建 TestViewController 實(shí)例時(shí)杏死,可以通過以下兩種方法:
//第一種
[[TestViewController alloc]initWithNibName:@"ViewController" bundle:nil];
//第二種
[[TestViewController alloc]init];
我們經(jīng)常使用的是第二種創(chuàng)建方法泵肄,其實(shí)第二種方法默認(rèn)實(shí)現(xiàn)了第一種的方法,只不過兩個(gè)參數(shù)默認(rèn)傳的是 nil淑翼。
當(dāng) TestVeiwController 通過 xib 加載的時(shí)候腐巢,看下 viewDidLoad 之前發(fā)生了什么:
-[TestViewController initWithNibName:bundle:]
-[TestViewController init]
-[TestViewController loadView]
-[TestViewController viewDidLoad]
無(wú) 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];
}
控制臺(tái)輸出:
-[TestViewController initWithCoder:]
-[TestViewController awakeFromNib]
-[TestViewController loadView]
-[TestViewController viewDidLoad]
我們可以看到通過 storyboard 實(shí)例化與 init
實(shí)例化在 loadView
方法調(diào)用之前走的是不同的方法。我們看下這幾個(gè)方法的不同:
initWithNibName:bundle:
此方法發(fā)生在 nib 加載之前玄括。
調(diào)用此方法進(jìn)行 Controller 初始化冯丙,與 nib 加載無(wú)關(guān)。nib 的加載是懶加載遭京,當(dāng) Controller 需要加載其視圖時(shí)胃惜,才會(huì)加載此方法中指定的 nib。
可以看出該方法初始化的 Controller 不是從 nib 創(chuàng)建的哪雕。
initWithCoder
此方法發(fā)生在 nib 加載期間船殉。
所有 archived 對(duì)象的初始化使用此方法。nib 中存儲(chǔ)的對(duì)象就是 archived 對(duì)象斯嚎,所以此方法是 nib 加載對(duì)象時(shí)使用的初始化方法利虫。
當(dāng)從 nib 創(chuàng)建 UIViewController 時(shí)使用此方法。
awakeFromNib
此方法發(fā)生在 nib 中所有對(duì)象都已完全加載完之后堡僻。
如果 initWithCoder
是 unarchiving 開始糠惫,那此方法就是結(jié)束。
loadView 與 veiwDidLoad
在此方法中創(chuàng)建視圖钉疫。
我們可以通過下圖來(lái)理解它的邏輯:
每次訪問 view 時(shí)硼讽,就會(huì)調(diào)用 self.view 的 get 方法,在 get 方法中判斷self.view==nil
牲阁,不為 nil 就直接返回 view固阁,等于 nil 就去調(diào)用 loadView 方法壤躲。loadView 方法會(huì)去判斷有無(wú)指定 storyBord/Xib 文件,如果有就去加載 storyBord/Xib 描述的控制器 view您炉,如果沒有則系統(tǒng)默認(rèn)創(chuàng)建一個(gè)空的 view柒爵,賦給 self.view。loadView 方法有可能被多次調(diào)用(每當(dāng)訪問 self.view 并且為 nil 時(shí)就會(huì)調(diào)用一次)赚爵;
系統(tǒng)會(huì)自動(dòng)為我們加載 view棉胀,我們完全沒必要手動(dòng)創(chuàng)建 view。
viewWillAppear
視圖將要被展示的時(shí)候調(diào)用冀膝。
其調(diào)用的時(shí)機(jī)與視圖所在層次有關(guān)唁奢。例如我們常用的 push 與 present 操作改變了當(dāng)前視圖層次,都會(huì)觸發(fā)此方法窝剖。
1麻掸、那么 UIAlertController 也是 present 操作怎么沒有觸發(fā)呢?
因?yàn)?UIAlertController 在另一個(gè) window 上赐纱,view 在自己所在的 window 中層次并沒有改變脊奋,所以不會(huì)觸發(fā),同理在鎖屏以及進(jìn)入后臺(tái)時(shí)也不會(huì)觸發(fā)疙描。
2诚隙、如果控制器 B 被展示在另一個(gè)控制器 A 的 popover 中,那么被展示的控制器 B 在消失后起胰,控制器 A 并不會(huì)調(diào)用此方法久又。
官方原文:
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];
當(dāng)我們將 BViewController 從 AViewController 中移除后效五,并不會(huì)觸發(fā) AViewController 的 viewWillAppear
方法地消。
viewDidAppear
視圖渲染完成后調(diào)用,與viewWillAppear
配套使用畏妖。
viewWillLayoutSubviews 與 viewDidLayoutSubviews
這兩個(gè)方法發(fā)生在 viewWillAppear
與 viewDidAppear
之間脉执。
-
viewWillLayoutSubviews
控制器將要布局 view 的子控件時(shí)調(diào)用,默認(rèn)實(shí)現(xiàn)為空戒劫。此時(shí)子控件的大小還沒有設(shè)置好半夷。
-
viewDidLayoutSubviews
控制器已經(jīng)布局 view 的子控件時(shí)調(diào)用,默認(rèn)實(shí)現(xiàn)為空谱仪。此時(shí)子控件的大小才被設(shè)置好,這里才是獲取子視圖大小的正確位置否彩。
viewWillDisappear 與 viewDidDisappear
viewWillDisappear
與viewDidDisappear
配套使用疯攒。
-
viewWillDisappear
視圖將要消失時(shí)調(diào)用
-
viewDidDisappear
視圖完全消失后調(diào)用
兩個(gè)方法的調(diào)用時(shí)機(jī)同viewWillAppear
和viewDidAppear
道理相同。
didReceiveMemoryWarning 與 viewDidUnload
這兩個(gè)方法是收到內(nèi)存警告時(shí)調(diào)用的列荔。
- viewDidUnload
在 iOS5 以及之前使用的方法敬尺,iOS6 及之后已經(jīng)廢棄枚尼。在收到內(nèi)存警告時(shí),在此方法中將 view 置為 nil;
- didReceiveMemoryWarning
收到內(nèi)存警告時(shí)砂吞,系統(tǒng)自動(dòng)調(diào)用此方法署恍,回收占用大量?jī)?nèi)存的視圖數(shù)據(jù)。我們一般不需要在這里做額外的操作蜻直。如果要自己處理一些額外內(nèi)存盯质,重寫時(shí)需要調(diào)用父類方法,即[super didReceiveMemoryWarning]
。
dealloc
UIViewController 釋放時(shí)調(diào)用此方法。UIViewController 的生命周期到此結(jié)束劣挫。
當(dāng)我們重寫此方法時(shí)寻馏,ARC 環(huán)境下不需要調(diào)用父類方法,MRC 環(huán)境下需要調(diào)用父類方法泰佳,即[super dealloc]
。
小結(jié)
本篇主要介紹了 APP 的生命周期,以及 UIViewController 的生命周期压储,對(duì)我們程序開發(fā)的過程有了更清晰的認(rèn)識(shí)。
參考資料: