UIViewController的生命周期

最近看了幾篇博客,在這里對ViewConroller的生命周期做一個總結,抽絲剝繭吧,感覺有道理的拿出來匯總一下 。

一、結構

按結構可以對iOS的所有ViewController分成兩類:
1、主要用于展示內容的ViewController,這種ViewController主要用于為用戶展示內容,并與用戶交互,如UITableViewController,UIViewController。
2、用于控制和顯示其他ViewController的ViewController。這種ViewController一般都是一個ViewController的容器。如UINavigationController,UITabbarController。它們都有一個屬性:viewControllers。其中UINavigationController表示一種Stack式結構,push一個ViewController或pop一次,因此后一個ViewController一般會依賴前一個ViewController。而UITabbarController表示一個Array結構,各個ViewController是并列的。
第一種ViewController會經常被繼承,用來顯示不同的數據給用戶。而第二種很少被繼承,除非你真的需要自定義它。

二、ViewController相關函數以及執行順序
當一個視圖控制器被創建,并在屏幕上顯示的時候。 代碼的執行順序:**
1、alloc                   創建對象,分配空間
2、init (initWithNibName)  初始化對象,初始化數據
3、loadView?????????????   從nib載入視圖 ,通常這一步不需要去干涉。除非你沒有使用xib文件創建視圖
4、viewDidLoad?????????    載入完成,可以進行自定義數據以及動態創建其他控件
5、viewWillAppear???????   視圖將出現在屏幕之前,馬上這個視圖就會被展現在屏幕上了
6、viewDidAppear??????    ?視圖已在屏幕上渲染完成

當一個視圖被移除屏幕并且銷毀的時候的執行順序,這個順序差不多和上面的相反。**
1、viewWillDisappear?????  視圖將被從屏幕上移除之前執行
2、viewDidDisappear????    視圖已經被從屏幕上移除,用戶看不到這個視圖了
3、dealloc??????????????  ?視圖被銷毀,此處需要對你在init和viewDidLoad中創建的對象進行釋放

關于viewDidUnload
在發生內存警告的時候如果本視圖不是當前屏幕上正在顯示的視圖的話, viewDidUnload將會被執行,本視圖的所有子視圖將被銷毀,以釋放內存,此時開發者需要手動對viewLoad、viewDidLoad中創建的對象釋放內存。 因為當這個視圖再次顯示在屏幕上的時候,viewLoad、viewDidLoad 再次被調用,以便再次構造視圖。

三、Controller和View的生命周期

這里指的View是指Controller的View。它作為Controler的屬性,生命周期在Controller的生命周期內。就是說你的Controller不能在view釋放前就釋放了。


Controller和View的生命周期.png

當你alloc并init了一個ViewController時,這個ViewController應該是還沒有創建view的。ViewController的view是使用了lazyInit方式創建,就是說你調用的view屬性的getter:[self view]。在getter里會先判斷view是否創建,如果沒有創建,那么會調用loadView來創建view。loadView完成時會繼續調用viewDidLoad。loadView和viewDidLoad的一個區別就是:loadView時還沒有view。而viewDidLoad時view以及創建好了。
當view被添加其他view中之前時,會調用viewWillAppear,而之后會調用viewDidAppear。
當view從其他view中移出之前時,會調用viewWillDisAppear,而之后會調用viewDidDisappear。
當view不在使用,而且是disappeared,受到內存警告時,那么viewController會將view釋放并將其指向nil。

四、App運行時的調用順序

函數詳解
1)- (void)viewDidLoad;
一個APP在載入時會先通過調用loadView方法或者載入IB中創建的初始界面的方法,將視圖載入到內存中。然后會調用viewDidLoad方法來進行進一步的設置。通常,我們對于各種初始數據的載入,初始設定等很多內容,都會在這個方法中實現,所以這個方法是一個很常用,很重要的方法。
但是要注意,這個方法只會在APP剛開始加載的時候調用一次,以后都不會再調用它了,所以只能用來做初始設置。
2)- (void)viewDidUnload;
在內存足夠的情況下,軟件的視圖通常會一直保存在內存中,但是如果內存不夠,一些沒有正在顯示的viewcontroller就會收到內存不夠的警告,然后就會釋放自己擁有的視圖,以達到釋放內存的目的。但是系統只會釋放內存,并不會釋放對象的所有權,所以通常我們需要在這里將不需要在內存中保留的對象釋放所有權,也就是將其指針置為nil。
這個方法通常并不會在視圖變換的時候被調用,而只會在系統退出或者收到內存警告的時候才會被調用。但是由于我們需要保證在收到內存警告的時候能夠對其作出反應,所以這個方法通常我們都需要去實現。
另外,即使在設備上按了Home鍵之后,系統也不一定會調用這個方法,因為IOS4之后,系統允許將APP在后臺掛起,并將其繼續滯留在內存中,因此,viewcontroller并不會調用這個方法來清除內存。
3)- (void)viewWillAppear:(BOOL)animated;
系統在載入所有數據后,將會在屏幕上顯示視圖,這時會先調用這個方法。通常我們會利用這個方法,對即將顯示的視圖做進一步的設置。例如,我們可以利用這個方法來設置設備不同方向時該如何顯示。
另外一方面,當APP有多個視圖時,在視圖間切換時,并不會再次載入viewDidLoad方法,所以如果在調入視圖時,需要對數據做更新,就只能在這個方法內實現了。所以這個方法也非常常用。
4)- (void)viewDidAppear:(BOOL)animated;
有時候,由于一些特殊的原因,我們不能在viewWillApper方法里,對視圖進行更新。那么可以重寫這個方法,在這里對正在顯示的視圖進行進一步的設置。
5)- (void)viewWillDisappear:(BOOL)animated;
在視圖變換時,當前視圖在即將被移除、或者被覆蓋時,會調用這個方法進行一些善后的處理和設置。
由于在IOS4之后,系統允許將APP在后臺掛起,所以在按了Home鍵之后,系統并不會調用這個方法,因為就這個APP本身而言,APP顯示的view,仍是掛起時候的view,所以并不會調用這個方法。
6)- (void)viewDidDisappear:(BOOL)animated;
我們可以重寫這個方法,對已經消失,或者被覆蓋,或者已經隱藏了的視圖做一些其他操作。
流程概述

運行APP —> 載入視圖 —> 調用viewDidLoad方法 —> 調用viewWillAppear方法 —> viewWillLayoutSubviews —> viewDidLayoutSubviews—> 調用viewDidAppear方法 —>   正常運行
APP需要調用另一個view—> 調用viewWillDisappear—>調用viewDidDisappear—>收到內存警告didReceiveMemoryWarning —>釋放對象所有權delloc

流程圖

流程圖.png

五、注意

1、init里不要出現創建view的代碼。良好的設計,在init里應該只有相關數據的初始化,而且這些數據都是比較關鍵的數據。init里不要掉self.view,否則會導致viewcontroller創建view。(因為view是lazyinit的)。
2、loadView中只初始化view,一般用于創建比較關鍵的view如tableViewController的tabView,UINavigationController的navgationBar,不可掉用view的getter(在掉super loadView前),最好也不要初始化一些非關鍵的view。如果你是從nib文件中創建的viewController在這里一定要首先調用super的loadView方法,但建議不要重載這個方法。
3、viewDidLoad 這時候view已經有了,最適合創建一些附加的view和控件了。有一點需要注意的是,viewDidLoad會調用多次(viewcontroller可能多次載入view,參見圖2)。
4、viewWillAppear 這個一般在view被添加到superview之前,切換動畫之前調用。在這里可以進行一些顯示前的處理。比如鍵盤彈出,一些特殊的過程動畫(比如狀態條和navigationbar顏色)。
5、viewDidAppear 一般用于顯示后,在切換動畫后,如果有需要的操作,可以在這里加入相關代碼。
6、viewDidUnload 這時候viewController的view已經是nil了。由于這一般發生在內存警告時,所以在這里你應該將那些不在顯示的view釋放了。比如你在viewcontroller的view上加了一個label,而且這個label是viewcontroller的屬性,那么你要把這個屬性設置成nil,以免占用不必要的內存,而這個label在viewDidLoad時會重新創建。
7、從nib文件加載視圖的controller,只要不釋放,在每次viewWillAppear時都會調用layoutSubviews方法,有時甚至會在viewDidAppear后在調用一次layoutSubviews,而從代碼加載視圖的則只會在開始調用一次,之后都不會,所以注意,在layoutSubviews中寫相關的布局代碼十分危險。

六、代碼示例
#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;
}

// 加載視圖(默認從nib)
- (void)loadView {
    NSLog(@"%s", __FUNCTION__);
   // [super loadView];
    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-04-24 00:16:32.099 ViewController[16899:41923212] -[ViewController initWithCoder:]
2017-04-24 00:16:32.101 ViewController[16899:41923212] -[ViewController loadView]
2017-04-24 00:16:32.102 ViewController[16899:41923212] -[ViewController viewDidLoad]
2017-04-24 00:16:32.102 ViewController[16899:41923212] -[ViewController viewWillAppear:]
2017-04-24 00:16:32.104 ViewController[16899:41923212] -[ViewController viewWillLayoutSubviews]
2017-04-24 00:16:32.104 ViewController[16899:41923212] -[ViewController viewDidLayoutSubviews]
2017-04-24 00:16:32.104 ViewController[16899:41923212] -[ViewController viewWillLayoutSubviews]
2017-04-24 00:16:32.104 ViewController[16899:41923212] -[ViewController viewDidLayoutSubviews]
2017-04-24 00:16:32.106 ViewController[16899:41923212] -[ViewController viewDidAppear:]

分析

  1. initWithNibName:bundle:
    初始化UIViewController,執行關鍵數據初始化操作,非StoryBoard創建UIViewController都會調用這個方法。
    注意: 不要在這里做View相關操作,View在loadView方法中才初始化。
  1. initWithCoder:
    如果使用StoryBoard進行視圖管理,程序不會直接初始化一個UIViewController,StoryBoard會自動初始化或在segue被觸發時自動初始化,因此方法initWithNibName:bundle不會被調用,但是initWithCoder會被調用。
  1. loadView
    當訪問UIViewController的View屬性時,View如果此時為nil,那么ViewController會自動調用loadView方法來初始化一個UIView并賦值給UIViewController的View;如果沒有重載lodaView方法,則UIViewController會從nib或StoryBoard中查找默認的loadView,默認的loadView會返回一個空白的UIView對象。
    注意:在view初始化之前,不能先調用view的getter方法,否則將導致死循環(除非先調用[super loadView])
  1. viewDidLoad
    當loadView將view載入內存中,會進一步調用viewDidLoad方法來進行進一步設置。通常,我們對于各種初始化數據的載入,初始設定等很多內容都會在這個方法中實現。
  1. viewWillAppear
    系統在載入所有的數據后,將會在屏幕上顯示視圖,這時會先調用這個方法,通常我們會在這個方法對即將顯示的視圖做進一步的設置。比如,設置設備不同方向時該如何顯示;設置狀態欄方向、設置視圖顯示樣式等。
    另一方面,當APP有多個視圖時,上下級視圖切換是也會調用這個方法,如果在調入視圖時,需要對數據做更新,就只能在這個方法內實現。
  1. viewWillLayoutSubviews
    view 即將布局其Subviews。 比如view的bounds改變了(例如:狀態欄從不顯示到顯示,視圖方向變化),要調整Subviews的位置,在調整之前要做的工作可以放在該方法中實現
  1. viewDidLayoutSubviews
    view已經布局其Subviews,這里可以放置調整完成之后需要做的工作。
  1. viewDidAppear
    在view被添加到視圖層級中以及多視圖,上下級視圖切換時調用這個方法,在這里可以對正在顯示的視圖做進一步的設置。
  1. viewWillDisappear
    在視圖切換是,當前視圖在即將被移除、或被覆蓋是,會調用該方法,此時還沒有調用removeFromSuperview。
  1. viewDidDisappear
    view已經消失或被覆蓋,此時已經調用removeFromSuperView;
  1. dealloc
    視圖被銷毀,此次需要對你在init和viewDidLoad中創建的對象進行釋放。
  1. didReceiveMemoryWarning
    在內存足夠的情況下,app的視圖通常會一直保存在內存中,但是如果內存不夠,一些沒有正在顯示的viewController就會收到內存不夠的警告,然后就會釋放自己擁有的視圖,以達到釋放內存的目的。但是系統只會釋放內存,并不會釋放對象的所有權,所以通常我們需要在這里將不需要顯示在內存中保留的對象釋放它的所有權,將其指針置nil。

------整理

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

推薦閱讀更多精彩內容