App、UIViewController、UIView 生命周期

一、App的生命周期

當(dāng)我們打開(kāi) APP 時(shí),程序一般都是從 main 函數(shù)開(kāi)始運(yùn)行的,那么我們先來(lái)看下 Xcode 自動(dòng)生成的 main.m 文件:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

這個(gè)默認(rèn)的 iOS 程序就是從 main 函數(shù)開(kāi)始執(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)用戶(hù)手動(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 類(lèi)或其子類(lèi)。這個(gè)參數(shù)默認(rèn)為 nil,則代表 UIApplication 類(lèi)。UIApplication 是單例模式,一個(gè)應(yīng)用程序只有一個(gè) UIApplication 對(duì)象或子對(duì)象。
  • delegateClassName:代理,默認(rèn)生成的是 AppDelegate 類(lèi),這個(gè)類(lèi)主要用于監(jiān)聽(tīng)整個(gè)應(yīng)用程序生命周期的各個(gè)事件,當(dāng)UIApplication運(yùn)行過(guò)程中引發(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ì)開(kāi)啟一個(gè)消息循環(huán)不斷監(jiān)聽(tīng)?wèi)?yīng)用程序的各個(gè)活動(dòng),當(dāng)應(yīng)用程序生命周期發(fā)生改變 UIApplication 就會(huì)調(diào)用代理對(duì)應(yīng)的方法。

既然應(yīng)用程序 UIApplication 是通過(guò)代理和外部交互的,那么我們就有必要清楚 AppDelegate 的操作細(xì)節(jié),在這個(gè)類(lèi)中定義了生命周期的各個(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)
程序已經(jīng)獲得焦點(diǎn)

  • 按下 home 鍵

程序?qū)⒁ソ裹c(diǎn)
程序已經(jīng)進(jìn)入后臺(tái)

  • 重新進(jìn)入程序

程序?qū)⒁M(jìn)入前臺(tái)
程序已經(jīng)獲得焦點(diǎn)

  • 下拉狀態(tài)欄

程序?qū)⒁ソ裹c(diǎn)

  • 狀態(tài)欄收回

程序已經(jīng)獲得焦點(diǎn)

  • 上拉控制中心

程序?qū)⒁ソ裹c(diǎn)

  • 收回控制中心

程序已經(jīng)獲得焦點(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止

相信通過(guò)上面運(yùn)行過(guò)程大家會(huì)對(duì)整個(gè)運(yùn)行周期有個(gè)大概了解。比較容易混淆的地方就是應(yīng)用程序進(jìn)入前臺(tái)、激活、失去焦點(diǎn)、進(jìn)入后臺(tái),這幾個(gè)方法大家要清楚。如果一個(gè)應(yīng)用程序失去焦點(diǎn)那么意味著用戶(hù)當(dāng)前無(wú)法進(jìn)行交互操作,因此一般會(huì)先失去焦點(diǎn)再進(jìn)入后臺(tái)防止進(jìn)入后臺(tái)過(guò)程中用戶(hù)誤操作;如果一個(gè)應(yīng)用程序進(jìn)入前臺(tái)也是類(lèi)似的,會(huì)先進(jìn)入前臺(tái)再獲得焦點(diǎn),這樣進(jìn)入前臺(tái)過(guò)程中未完全準(zhǔn)備好的情況下用戶(hù)無(wú)法操作。另外一般如果應(yīng)用程序要保存用戶(hù)數(shù)據(jù)會(huì)在注銷(xiāo)激活中進(jìn)行(而不是在進(jìn)入后臺(tái)方法中進(jìn)行),因?yàn)槿绻脩?hù)雙擊Home不會(huì)進(jìn)入后臺(tái)只會(huì)注銷(xiāo)激活;如果用戶(hù)恢復(fù)應(yīng)用狀態(tài)一般在進(jìn)入激活狀態(tài)時(shí)處理(而不是在進(jìn)入前臺(tái)方法中進(jìn)行),因?yàn)橛脩?hù)可能是從任務(wù)欄直接返回應(yīng)用,此時(shí)不會(huì)執(zhí)行進(jìn)入前臺(tái)操作。

當(dāng)然,上面的事件并不是所有AppDelegate事件,而是最常用的一些事件,其他事件大家可以查閱官方文檔,例如-(void)applicationDidReceiveMemoryWarning:(UIApplication *)application;用于在內(nèi)存占用過(guò)多發(fā)出內(nèi)存警告時(shí)調(diào)用并通知對(duì)應(yīng)的ViewController調(diào)用其內(nèi)存回收方法。這里簡(jiǎn)單以圖形方式描述一下應(yīng)用程序的調(diào)用過(guò)程:


二、UIViewController 的生命周期

先上經(jīng)典圖


#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

我們?cè)趧?chuàng)建 TestViewController 實(shí)例時(shí),可以通過(guò)以下兩種方法:

//第一種
[[TestViewController alloc]initWithNibName:@"ViewController" bundle:nil];

//第二種    
[[TestViewController alloc]init];

我們經(jīng)常使用的是第二種創(chuàng)建方法,其實(shí)第二種方法默認(rèn)實(shí)現(xiàn)了第一種的方法,只不過(guò)兩個(gè)參數(shù)默認(rèn)傳的是 nil。

當(dāng) TestVeiwController 通過(guò) 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 通過(guò) storyboard 加載:

-[TestViewController initWithCoder:]
-[TestViewController awakeFromNib]
-[TestViewController loadView]
-[TestViewController viewDidLoad]

我們可以看到通過(guò) 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 開(kāi)始,那此方法就是結(jié)束。

  • loadViewveiwDidLoad
    在此方法中創(chuàng)建視圖。

我們可以通過(guò)下圖來(lái)理解它的邏輯:


每次訪問(wèn) view 時(shí),就會(huì)調(diào)用 self.viewget 方法,在 get 方法中判斷self.view==nil,不為nil就直接返回 view,等于 nil就去調(diào)用 loadView 方法。loadView 方法會(huì)去判斷有無(wú)指定 storyBord/Xib 文件,如果有就去加載 storyBord/Xib 描述的控制器 view,如果沒(méi)有則系統(tǒng)默認(rèn)創(chuàng)建一個(gè)空的 view,賦給 self.view。loadView方法有可能被多次調(diào)用(每當(dāng)訪問(wèn) self.view 并且為 nil時(shí)就會(huì)調(diào)用一次);

系統(tǒng)會(huì)自動(dòng)為我們加載 view,我們完全沒(méi)必要手動(dòng)創(chuàng)建 view

  • viewWillAppear
    視圖將要被展示的時(shí)候調(diào)用。

其調(diào)用的時(shí)機(jī)與視圖所在層次有關(guān)。例如我們常用的 push 與 present 操作改變了當(dāng)前視圖層次,都會(huì)觸發(fā)此方法。

1、那么 UIAlertController 也是 present 操作怎么沒(méi)有觸發(fā)呢?

因?yàn)?UIAlertController 在另一個(gè) window上,view 在自己所在的 window 中層次并沒(méi)有改變,所以不會(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配套使用。

  • viewWillLayoutSubviewsviewDidLayoutSubviews

這兩個(gè)方法發(fā)生在 viewWillAppear 與 viewDidAppear 之間。

  • viewWillLayoutSubviews
    控制器將要布局 view 的子控件時(shí)調(diào)用,默認(rèn)實(shí)現(xiàn)為空。此時(shí)子控件的大小還沒(méi)有設(shè)置好。
  • viewDidLayoutSubviews
    控制器已經(jīng)布局 view 的子控件時(shí)調(diào)用,默認(rèn)實(shí)現(xiàn)為空。此時(shí)子控件的大小才被設(shè)置好,這里才是獲取子視圖大小的正確位置。
  • viewWillDisappearviewDidDisappear
  • viewWillDisappear
    視圖將要消失時(shí)調(diào)用
  • viewDidDisappear
    視圖完全消失后調(diào)用
  • didReceiveMemoryWarningviewDidUnload

這兩個(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)存,重寫(xiě)時(shí)需要調(diào)用父類(lèi)方法,即[super didReceiveMemoryWarning]
  • dealloc
    UIViewController 釋放時(shí)調(diào)用此方法。UIViewController 的生命周期到此結(jié)束。
    當(dāng)我們重寫(xiě)此方法時(shí),ARC 環(huán)境下不需要調(diào)用父類(lèi)方法,MRC 環(huán)境下需要調(diào)用父類(lèi)方法,即[super dealloc]。

三、UIView 的生命周期

UIView生命周期相關(guān)函數(shù):

//構(gòu)造方法,初始化時(shí)調(diào)用,不會(huì)調(diào)用init方法
- (instancetype)initWithFrame:(CGRect)frame;
//添加子控件時(shí)調(diào)用
- (void)didAddSubview:(UIView *)subview ;
//構(gòu)造方法,內(nèi)部會(huì)調(diào)用initWithFrame方法
- (instancetype)init;
//xib歸檔初始化視圖后調(diào)用,如果xib中添加了子控件會(huì)在didAddSubview方法調(diào)用后調(diào)用
- (instancetype)initWithCoder:(NSCoder *)aDecoder;
//喚醒xib,可以布局子控件
- (void)awakeFromNib;
//父視圖將要更改為指定的父視圖,當(dāng)前視圖被添加到父視圖時(shí)調(diào)用
- (void)willMoveToSuperview:(UIView *)newSuperview;
//父視圖已更改
- (void)didMoveToSuperview;
//其窗口對(duì)象將要更改
- (void)willMoveToWindow:(UIWindow *)newWindow;
//窗口對(duì)象已經(jīng)更改
- (void)didMoveToWindow;
//布局子控件
- (void)layoutSubviews;
//繪制視圖
- (void)drawRect:(CGRect)rect;
//從父控件中移除
- (void)removeFromSuperview;
//銷(xiāo)毀
- (void)dealloc;
//將要移除子控件
- (void)willRemoveSubview:(UIView *)subview;

1.沒(méi)有子控件的UIView

顯示過(guò)程:

//(superview)
- (void)willmovetosuperview:(nullable UIView *)newSuperview
- (void)didmovetosuperview

//(window)
- (void)willmovetowindow:(nullable UIWindow *)newWindow
- (void)didmovetowindow

- (void)layoutsubviews

移出過(guò)程:

//(window)
- (void)willmovetowindow:(nullable UIWindow *)newWindow
- (void)didmovetowindow

//(superview)
- (void)willmovetosuperview:(nullable UIView *)newSuperview
- (void)didmovetosuperview

- (void)removeFromSuperview
- (void)dealloc

但是在移出時(shí)newWindow和newSuperview 都是nil。

2.包含子控件的UIView

當(dāng)增加一個(gè)子控件時(shí),就會(huì)執(zhí)行 didAddSubview,之后也會(huì)執(zhí)行一次layoutsubview。
在view釋放后,執(zhí)行完,dealloc就會(huì)多次執(zhí)行willRemoveSubview.先add的view,先釋放掉。

3.layoutsubview

在上面的方法中,經(jīng)常發(fā)現(xiàn)layoutsubview會(huì)被調(diào)用,下面說(shuō)下layoutsubview的調(diào)用情況:
1、addSubview會(huì)觸發(fā)layoutSubviews,如果addSubview 如果連續(xù)2個(gè) 只會(huì)執(zhí)行一次,具體原因下面說(shuō)。
2、設(shè)置view的Frame會(huì)觸發(fā)layoutSubviews,必須是frame的值設(shè)置前后發(fā)生了變化。
3、滾動(dòng)一個(gè)UIScrollView會(huì)觸發(fā)layoutSubviews。
4、旋轉(zhuǎn)Screen會(huì)觸發(fā)父UIView上的layoutSubviews事件。
5、改變一個(gè)UIView大小的時(shí)候也會(huì)觸發(fā)父UIView上的layoutSubviews事件。

TIP
1、如果要立即執(zhí)行l(wèi)ayoutsubview,
要先調(diào)用[view setNeedsLayout],把標(biāo)記設(shè)為需要布局.
然后馬上調(diào)用[view layoutIfNeeded],實(shí)現(xiàn)布局.

其中的原理是:執(zhí)行setNeedsLayout后會(huì)在receiver標(biāo)上一個(gè)需要被重新布局的標(biāo)記,在系統(tǒng)runloop的下一個(gè)周期自動(dòng)調(diào)用layoutSubviews。
這樣刷新會(huì)產(chǎn)生延遲,所以我們需要馬上執(zhí)行l(wèi)ayoutIfNeeded。就會(huì)開(kāi)始遍歷subviews的鏈,判斷該receiver是否需要layout。如果需要立即執(zhí)行l(wèi)ayoutsubview

2、addSubview

每一個(gè)視圖只能有唯一的一個(gè)父視圖。如果當(dāng)前操作視圖已經(jīng)有另外的一個(gè)父視圖,則addsubview的操作會(huì)把它先從上一個(gè)父視圖中移除(包括響應(yīng)者鏈),再加到新的父視圖上面。

連續(xù)2次的addSubview,只會(huì)執(zhí)行一次layoutsubview。因?yàn)橐淮蔚膔unLoop結(jié)束后,如果有需要刷新,執(zhí)行一次即可。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容