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

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 參考自滾滾貓的《iOS APP生命周期 和 UIViewController的生命周期》,滄州寧少的《iOS Ap...
    小暖風閱讀 2,973評論 0 6
  • Swift1> Swift和OC的區別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,135評論 1 32
  • load初始化方法<加載到內存就會執行,不需要觸發,且只會調用一次> + (void)load 只要加載內存中就會...
    flowerflower閱讀 738評論 1 2
  • ViewController生命周期 按照執行順序排列: initWithCoder:通過nib文件初始化時觸發。...
    隱身人閱讀 694評論 3 7
  • 譯者注:本文是對 Apple 官方文檔的翻譯,原文地址為:https://developer.apple.com/...
    ampire_dan閱讀 7,493評論 0 13