1. 程式人生 > 實用技巧 >iOS程式執行順序和UIViewController 的生命週期(整理)

iOS程式執行順序和UIViewController 的生命週期(整理)

UIView的生命週期

言葉之庭.jpeg

一. iOS程式的啟動執行順序

程式啟動順序圖

iOS啟動原理圖.png

具體執行流程

  1. 程式入口
    進入main函式,設定AppDelegate稱為函式的代理

  2. 程式完成載入
    [AppDelegate application:didFinishLaunchingWithOptions:]

  3. 建立window視窗

  4. 程式被啟用
    [AppDelegate applicationDidBecomeActive:]

  5. 當點選command+H時(針對模擬器,手機是當點選home鍵)
    程式取消啟用狀態
    [AppDelegate applicationWillResignActive:];


    程式進入後臺
    [AppDelegate applicationDidEnterBackground:];

  6. 點選進入工程
    程式進入前臺
    [AppDelegate applicationWillEnterForeground:]
    程式被啟用
    [AppDelegate applicationDidBecomeActive:];

分析

1. 對於applicationWillResignActive(非活動)applicationDidEnterBackground(後臺)這兩個的區別

  • applicationWillResignActive(非活動):
    比如當有電話進來或簡訊進來或鎖屏等情況下,這時應用程式掛起進入非活動狀態,也就是手機介面還是顯示著你當前的應用程式的視窗,只不過被別的任務強制佔用了,也可能是即將進入後臺狀態(因為要先進入非活動狀態然後進入後臺狀態)

  • applicationDidEnterBackground(後臺):
    指當前視窗不是你的App,大多數程式進入這個後臺會在這個狀態上停留一會,時間到之後會進入掛起狀態(Suspended)。如果你程式特殊處理後可以長期處於後臺狀態也可以執行。
    Suspended (掛起): 程式在後臺不能執行程式碼。系統會自動把程式變成這個狀態而且不會發出通知。當掛起時,程式還是停留在記憶體中的,當系統記憶體低時,系統就把掛起的程式清除掉,為前臺程式提供更多的記憶體。

如下圖所示:

活動和非活動.png

2.UIApplicationMain 函式解釋:


入口函式:
int
main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } UIApplicationMain(int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName);
  • argcargv 引數是為了與C語言保持一致。

  • principalClassName (主要類名) 和 ** delegateClassName (委託類名)**。
    (1) 如果principalClassName是nil,那麼它的值將從Info.plist去獲取,如果Info.plist沒有,則預設為UIApplicationprincipalClass這個類除了管理整個程式的生命週期之外什麼都不做,它只負責監聽事件然後交給delegateClass去做。
    (2) delegateClass 將在工程新建時例項化一個物件。NSStringFromClass([AppDelegate class])

  • AppDelegate 類實現檔案

 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSLog(@"--- %s ---",__func__);//__func__列印方法名
    return YES;
}


- (void)applicationWillResignActive:(UIApplication *)application {
     NSLog(@"--- %s ---",__func__);
}


- (void)applicationDidEnterBackground:(UIApplication *)application {
   NSLog(@"--- %s ---",__func__);
}


- (void)applicationWillEnterForeground:(UIApplication *)application {
   NSLog(@"--- %s ---",__func__);
}


- (void)applicationDidBecomeActive:(UIApplication *)application {
  NSLog(@"--- %s ---",__func__);
}


- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
     NSLog(@"--- %s ---",__func__);
}

- (void)applicationWillTerminate:(UIApplication *)application {
    NSLog(@"--- %s ---",__func__);
}

列印呼叫順序
啟動程式

 --- -[AppDelegate application:didFinishLaunchingWithOptions:] ---
 --- -[AppDelegate applicationDidBecomeActive:] ---

按下Command + H + SHIFT

--- -[AppDelegate applicationWillResignActive:] ---
--- -[AppDelegate applicationDidEnterBackground:] ---

重新點選 進入程式

--- -[AppDelegate applicationWillEnterForeground:] ---
--- -[AppDelegate applicationDidBecomeActive:] ---

選擇 模擬器的Simulate Memory Warning

--- -[AppDelegate applicationDidReceiveMemoryWarning:] ---

分析:

1.application:didFinishLaunchingWithOptions:
程式首次已經完成啟動時執行,一般在這個函式裡建立window物件,將程式內容通過window呈現給使用者。

  1. applicationWillResignActive(非活動)
    程式將要失去Active狀態時呼叫,比如有電話進來或者按下Home鍵,之後程式進入後臺狀態,對應的applicationWillEnterForeground(即將進入前臺)方法。

該函式裡面主要執行操作:
a . 暫停正在執行的任務
b. 禁止計時器
c. 減少OpenGL ES幀率
d. 若為遊戲應暫停遊戲

3.applicationDidEnterBackground(已經進入後臺)
對應applicationDidBecomeActive(已經變成前臺)

該方法用來:
a. 釋放共享資源
b. 儲存使用者資料(寫到硬碟)
c. 作廢計時器
d. 儲存足夠的程式狀態以便下次修復;

  1. applicationWillEnterForeground(即將進入前臺)
    程式即將進入前臺時呼叫,對應applicationWillResignActive(即將進入後臺)
    這個方法用來: 撤銷applicationWillResignActive中做的改變。

  2. applicationDidBecomeActive(已經進入前臺)
    程式已經變為Active(前臺)時呼叫。對應applicationDidEnterBackground(已經進入後臺)
    注意: 若程式之前在後臺,在此方法內重新整理使用者介面

  3. applicationWillTerminate
    程式即將退出時呼叫。記得儲存資料,如applicationDidEnterBackground方法一樣。

等候.jpeg

二. UIViewController 的 生命週期

程式碼 示例

#pragma mark --- life circle

// 非storyBoard(xib或非xib)都走這個方法
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    NSLog(@"%s", __FUNCTION__);
    if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
    
    }
    return self;
}

// 如果連線了串聯圖storyBoard 走這個方法
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
     NSLog(@"%s", __FUNCTION__);
    if (self = [super initWithCoder:aDecoder]) {
        
    }
    return self;
}

// xib 載入 完成
- (void)awakeFromNib {
    [super awakeFromNib];
     NSLog(@"%s", __FUNCTION__);
}

// 載入檢視(預設從nib)
- (void)loadView {
    NSLog(@"%s", __FUNCTION__);
    self.view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.view.backgroundColor = [UIColor redColor];
}

//檢視控制器中的檢視載入完成,viewController自帶的view載入完成
- (void)viewDidLoad {
    NSLog(@"%s", __FUNCTION__);
    [super viewDidLoad];
}


//檢視將要出現
- (void)viewWillAppear:(BOOL)animated {
    NSLog(@"%s", __FUNCTION__);
    [super viewWillAppear:animated];
}

// view 即將佈局其 Subviews
- (void)viewWillLayoutSubviews {
    NSLog(@"%s", __FUNCTION__);
    [super viewWillLayoutSubviews];
}

// view 已經佈局其 Subviews
- (void)viewDidLayoutSubviews {
    NSLog(@"%s", __FUNCTION__);
    [super viewDidLayoutSubviews];
}

//檢視已經出現
- (void)viewDidAppear:(BOOL)animated {
    NSLog(@"%s", __FUNCTION__);
    [super viewDidAppear:animated];
}

//檢視將要消失
- (void)viewWillDisappear:(BOOL)animated {
    NSLog(@"%s", __FUNCTION__);
    [super viewWillDisappear:animated];
}

//檢視已經消失
- (void)viewDidDisappear:(BOOL)animated {
    NSLog(@"%s", __FUNCTION__);
    [super viewDidDisappear:animated];
}

//出現記憶體警告  //模擬記憶體警告:點選模擬器->hardware-> Simulate Memory Warning
- (void)didReceiveMemoryWarning {
    NSLog(@"%s", __FUNCTION__);
    [super didReceiveMemoryWarning];
}

// 檢視被銷燬
- (void)dealloc {
    NSLog(@"%s", __FUNCTION__);
}

** 檢視 列印 結果 **

2017-03-01 18:03:41.577 ViewControllerLifeCircle[32254:401790] -[ViewController initWithCoder:]
2017-03-01 18:03:41.579 ViewControllerLifeCircle[32254:401790] -[ViewController awakeFromNib]
2017-03-01 18:03:41.581 ViewControllerLifeCircle[32254:401790] -[ViewController loadView]
2017-03-01 18:03:46.485 ViewControllerLifeCircle[32254:401790] -[ViewController viewDidLoad]
2017-03-01 18:03:46.486 ViewControllerLifeCircle[32254:401790] -[ViewController viewWillAppear:]
2017-03-01 18:03:46.487 ViewControllerLifeCircle[32254:401790] -[ViewController viewWillLayoutSubviews]
2017-03-01 18:03:46.488 ViewControllerLifeCircle[32254:401790] -[ViewController viewDidLayoutSubviews]
2017-03-01 18:03:46.488 ViewControllerLifeCircle[32254:401790] -[ViewController viewWillLayoutSubviews]
2017-03-01 18:03:46.488 ViewControllerLifeCircle[32254:401790] -[ViewController viewDidLayoutSubviews]
2017-03-01 18:03:46.490 ViewControllerLifeCircle[32254:401790] -[ViewController viewDidAppear:]
2017-03-01 19:03:13.308 ViewControllerLifeCircle[32611:427962] -[ViewController viewWillDisappear:]
2017-03-01 19:03:14.683 ViewControllerLifeCircle[32611:427962] -[ViewController viewDidDisappear:]
2017-03-01 19:03:14.683 ViewControllerLifeCircle[32611:427962] -[ViewController dealloc]
2017-03-01 19:12:05.927 ViewControllerLifeCircle[32611:427962] -[ViewController didReceiveMemoryWarning]

** 分析 **
1.initWithNibName:bundle:
初始化UIViewController,執行關鍵資料初始化操作,非StoryBoard建立UIViewController都會呼叫這個方法。
** 注意: 不要在這裡做View相關操作,ViewloadView方法中才初始化。**

2. initWithCoder:
如果使用StoryBoard進行檢視管理,程式不會直接初始化一個UIViewControllerStoryBoard會自動初始化或在segue被觸發時自動初始化,因此方法initWithNibName:bundle不會被呼叫,但是initWithCoder會被呼叫。

3. awakeFromNib
awakeFromNib方法被呼叫時,所有檢視的outletaction已經連線,但還沒有被確定,這個方法可以算作適合檢視控制器的例項化配合一起使用的,因為有些需要根據使用者喜好來進行設定的內容,無法存在storyBoardxib中,所以可以在awakeFromNib方法中被載入進來。

4. loadView
當執行到loadView方法時,如果檢視控制器是通過nib建立,那麼檢視控制器已經從nib檔案中被解檔並建立好了,接下來任務就是對view進行初始化。

loadView方法在UIViewController物件的view被訪問且為空的時候呼叫。這是它與awakeFromNib方法的一個區別。
假設我們在處理記憶體警告時釋放view屬性:self.view = nil。因此loadView方法在檢視控制器的生命週期內可能被呼叫多次。
loadView方法不應該直接被呼叫,而是由系統呼叫。它會載入或建立一個view並把它賦值給UIViewControllerview屬性。

在建立view的過程中,首先會根據nibName去找對應的nib檔案然後載入。如果nibName為空或找不到對應的nib檔案,則會建立一個空檢視(這種情況一般是純程式碼)

注意:在重寫loadView方法的時候,不要呼叫父類的方法。

5. viewDidLoad
loadViewview載入記憶體中,會進一步呼叫viewDidLoad方法來進行進一步設定。此時,檢視層次已經放到記憶體中,通常,我們對於各種初始化資料的載入,初始設定、修改約束、移除檢視等很多操作都可以這個方法中實現。

檢視層次(view hierachy):因為每個檢視都有自己的子檢視,這個檢視層次其實也可以理解為一顆樹狀的資料結構。而樹的根節點,也就是根檢視(root view),在UIViewController中以view屬性。它可以看做是其他所有子檢視的容器,也就是根節點。
6. viewWillAppear
系統在載入所有的資料後,將會在螢幕上顯示檢視,這時會先呼叫這個方法,通常我們會在這個方法對即將顯示的檢視做進一步的設定。比如,設定裝置不同方向時該如何顯示;設定狀態列方向、設定檢視顯示樣式等。

另一方面,當APP有多個檢視時,上下級檢視切換是也會呼叫這個方法,如果在調入檢視時,需要對資料做更新,就只能在這個方法內實現。

7. viewWillLayoutSubviews
view 即將佈局其Subviews。 比如viewbounds改變了(例如:狀態列從不顯示到顯示,檢視方向變化),要調整Subviews的位置,在調整之前要做的工作可以放在該方法中實現

8.viewDidLayoutSubviews
view已經佈局其Subviews,這裡可以放置調整完成之後需要做的工作。

9. viewDidAppear
在view被新增到檢視層級中以及多檢視,上下級檢視切換時呼叫這個方法,在這裡可以對正在顯示的檢視做進一步的設定。

10.viewWillDisappear
在檢視切換時,當前檢視在即將被移除、或被覆蓋是,會呼叫該方法,此時還沒有呼叫removeFromSuperview

11. viewDidDisappear
view已經消失或被覆蓋,此時已經呼叫removeFromSuperView;

12. dealloc
檢視被銷燬,此次需要對你在initviewDidLoad中建立的物件進行釋放。

13.didReceiveMemoryWarning
在記憶體足夠的情況下,app的檢視通常會一直儲存在記憶體中,但是如果記憶體不夠,一些沒有正在顯示的viewController就會收到記憶體不足的警告,然後就會釋放自己擁有的檢視,以達到釋放記憶體的目的。但是系統只會釋放記憶體,並不會釋放物件的所有權,所以通常我們需要在這裡將不需要顯示在記憶體中保留的物件釋放它的所有權,將其指標置nil

三. 檢視的生命歷程

  • [ViewController initWithCoder:][ViewController initWithNibName:Bundle]: 首先從歸檔檔案中載入UIViewController物件。即使是純程式碼,也會把nil作為引數傳給後者。
  • [UIView awakeFromNib]: 作為第一個方法的助手,方法處理一些額外的設定。
  • [ViewController loadView]:建立或載入一個view並把它賦值給UIViewControllerview屬性。
    -[ViewController viewDidLoad]: 此時整個檢視層次(view hierarchy)已經放到記憶體中,可以移除一些檢視,修改約束,載入資料等。
  • [ViewController viewWillAppear:]: 檢視載入完成,並即將顯示在螢幕上。還沒設定動畫,可以改變當前螢幕方向或狀態列的風格等。
  • [ViewController viewWillLayoutSubviews]即將開始子檢視位置佈局
  • [ViewController viewDidLayoutSubviews]用於通知檢視的位置佈局已經完成
  • [ViewController viewDidAppear:]:檢視已經展示在螢幕上,可以對檢視做一些關於展示效果方面的修改。
  • [ViewController viewWillDisappear:]:檢視即將消失
  • [ViewController viewDidDisappear:]:檢視已經消失
  • [ViewController dealloc:]:檢視銷燬的時候呼叫

四: 總結:

  • 只有init系列的方法,如initWithNibName需要自己呼叫,其他方法如loadViewawakeFromNib則是系統自動呼叫。而viewWill/Did系列的方法則類似於回撥和通知,也會被自動呼叫。

  • 純程式碼寫檢視佈局時需要注意,要手動呼叫loadView方法,而且不要呼叫父類的loadView方法。純程式碼和用IB的區別僅存在於loadView方法及其之前,程式設計時需要注意的也就是loadView方法。

  • 除了initWithNibNameawakeFromNib方法是處理檢視控制器外,其他方法都是處理檢視。這兩個方法在檢視控制器的生命週期裡只會呼叫一次。

三. 最後

送上一張喜歡的圖片:

初晴.jpeg

這是一篇總結筆記,大家有興趣可以蠻看一下,如果覺得不錯,麻煩給個喜歡,若發現有錯誤的地方請及時反饋,謝謝!



作者:林大鵬天地
連結:https://www.jianshu.com/p/d60b388b19f5
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。