iOS開發筆記(九):UIViewController的生命週期

常常會用到 ViewController,可是對它的生命週期一直沒有一個比較完整地理解,最近看了幾篇博客,在這裏對 ViewConroller 的生命週期作一個總結,一是爲了本身學習,二是爲了給你們一個參考,若有錯誤,歡迎指正。ios

ViewController 整體生命週期:app

ViewController 整體生命週期

1.ViewController 多種實例化方法

1.1 代碼

  • 經過 xib 加載函數

    先看一下 Demo 的文件結構,ViewController 爲 A 控制器,TestViewController 爲 B 控制器。佈局

    Demo文件結構

    當控制器 view 經過 xib 加載的時候,可能會出現三種狀況:學習

    • 指定xib名稱(OtherViewController.xib)動畫

      TestViewController *testVC = [[TestViewController alloc] initWithNibName:@"OtherViewController" bundle:nil];
      複製代碼

      當咱們指定了xib的名稱,loadView方 法就會去加載對應的 xib (OtherViewController.xib),最終是這個樣子的。spa

      指定xib名稱

    • 不指定 xib 名稱13d

      TestViewController *testVC = [[TestViewController alloc] initWithNibName:nil bundle:nil];
      複製代碼

      若是咱們不指定 xib 名稱,loadView 就會加載與控制器同名的 xib (TestViewController.xib),最終是這個樣子的。code

      不指定xib名稱1

    • 不指定 xib 名稱2cdn

      咱們先將 TestViewController.xib 這個文件刪除掉,這個時候,咱們再來運行程序,結果是這樣的。

      不指定xib名稱2

      根據上圖咱們能夠得知,當沒有指定 xib 名稱,且沒有與控制器同名的 xib 時,會加載前綴與控制器名相同而不帶 Controller 的 xib (TestView.xib)。

  • init

咱們常常會用代碼經過 init 手動建立一個 ViewController,以下:

TestViewController *testVC = [[TestViewController alloc] init];
複製代碼

其實本質仍是調用了 initWithNibName:bundle: 而且都傳入了 nil,只不過以上三種狀況都沒有知足,最終是這個樣子的。

init

1.2 storyboard 間接實例化(initWithCoder)

當你從 storyboard 初始化 ViewController 時,iOS 會使用 initWithCoder,而不是 initWithNibName 來初始化這個 ViewController,而後那個 storyboard 會在本身內部生成一個 nib (storyboard 實例化 view / ViewController 時,會把 nib 的信息放在 Coder 中,調用 initWithCoder)。

注意

storyboard 加載的是控制器及控制器 view,而 xib 加載的僅僅只是控制器的 view。之因此這麼說,咱們結合控制器的 awakeFromNib 方法解釋一下,顧名思義,當控制器從 nib 加載的時候就會調用這個方法,這個方法自己只是個信號、消息,是一個空方法 (即其默認實現爲空)。

先來看看經過 storyboard 加載的狀況:

//A控制器中代碼
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
	UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"TestViewController" bundle:nil];
	TestViewController *testVC = [storyboard instantiateInitialViewController];
	[self.navigationController pushViewController:testVC animated:YES];
}
//B控制器中代碼
- (void)awakeFromNib {
	NSLog(@"B經過nib加載");
}
複製代碼

經過storyboard加載

調用了 B 控制器的 awakeFromNib 方法。

將以前刪除的 TestViewController.xib 文件重寫添加進去,再來看經過xib加載的狀況:

//A控制器中代碼改成以下
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
	TestViewController *testVC =[[TestViewController alloc] init];
	[self.navigationController pushViewController:testVC animated:YES];
}
//B控制器中代碼不變
複製代碼

經過xib加載

B 控制器的 awakeFromNib 方法並無被調用。

因此,storyboard 加載的是控制器及控制器 view,而 xib 加載的僅僅只是控制器的 view。

2. loadView

這個方法中,要正式加載View了。首先咱們得知道,控制器 view 是經過懶加載的方式進行加載的,即用到的時候再加載。永遠不要主動調用這個方法。當咱們用到控制器 view 時,就會調用控制器 view 的 get 方法,在 get 方法內部,首先判斷 view 是否已經建立,若是已存在,則直接返回存在的 view,若是不存在,則調用控制器的 loadView 方法,在控制器沒有被銷燬的狀況下,loadView 也可能會被執行屢次。

當 ViewController 有如下狀況時都會在此方法中從 nib 文件加載 view :

  • ViewController 是從 storyboard 中實例化的。
  • 經過 initWithNibName:bundle: 初始化。
  • 在 App Bundle 中有一個 nib 文件名稱和本類名相同。

符合以上三點時,也就不須要重寫這個方法,不然你沒法獲得你想要的 nib 中的 view。

若是這個 ViewController 與 nib 無關,你能夠在這裏手寫 ViewController 的 view (這一步大概也能夠在 viewDidLoad 裏寫,實際上咱們也更常在 viewDidLoad 裏寫)。

是否須要調用 [super loadView]

loadView 方法的默認實現是這樣:先尋找有關可用的 nib 文件的信息,根據這個信息來加載 nib 文件,若是沒有有關 nib 文件的信息,默認實現會建立一個空白的 UIView 對象,而後讓這個對象成爲 controller 的主 view。

因此,重寫這個函數時,你也應該這麼作。並把子類的 view 賦給 view 屬性 (property) (你 create 的 view 必須是惟一的實例,而且不被其餘任何 controller 共享)。

若是你要進行進一步初始化你的 views,你應該在 viewDidLoad 函數中去作。在iOS 3.0 以及更高版本中,你應該重載 viewDidUnload 函數來釋聽任何對 view 的引用或者它裏面的內容(子 view 等等)。

回到關於 [super loadView] 的討論中,若是咱們的 ViewController 與 nib 有關,也就是說咱們不須要重寫 loadView 方法,也就不用關心 [super loadView]。而若是與 nib 無關,咱們須要重寫 loadView 方法,而 [super loadView] 根據上面的解釋就會生成一個空白的 view,這恐怕並不能知足咱們的需求,因此調用也沒有多大意義。

2.1 ViewController 加載 View 過程

ViewController 加載 View 過程

從圖中能夠看到,在 view 加載過程當中首先會調用 loadView 方法,在這個方法中主要完成一些關鍵 view 的初始化工做,好比 UINavigationViewController 和 UITabBarController 等容器類的 ViewController;接下來就是加載 view,加載成功後,會接着調用 viewDidLoad 方法,這裏要記住的一點是,在 loadView 以前,是沒有 view 的,也就是說,在這以前,view 尚未被初始化。完成 viewDidLoad 方法後,ViewController 裏面就成功的加載 view了,如上圖右下角所示。

死循環

若 loadView 沒有加載 view,即爲 nil,viewDidLoad 會一直調用 loadView 加載 view,所以構成了死循環,程序即卡死,因此咱們常在 ViewDidLoad 裏建立 view。

2.2 ViewController 卸載 View 過程

ViewController 卸載 View 過程

從圖中能夠看到,當系統發出內存警告時,會調用 didReceiveMemoeryWarning 方法,若是當前有能被釋放的 view,系統會調用 viewWillUnload 方法來釋放 view,完成後調用 viewDidUnload方法,至此,view 就被卸載了。此時本來指向 view 的變量要被置爲 nil,具體操做是在 viewDidUnload 方法中調用 self.myButton = nil。

3.viewDidLoad

當控制器的 loadView 方法執行完畢,view 被建立成功後,就會執行 viewDidLoad 方法,該方法與loadView 方法同樣,也有可能被執行屢次。在開發中,咱們可能從未遇到過執行屢次的狀況,那何時會執行屢次呢?

好比 A 控制器 push 出 B 控制器,此時,窗口顯示的是 B 控制器的 view,此時若是收到內存警告,咱們通常會將 A 控制器中沒用的變量及 view 銷燬掉,以後當咱們從 B 控制器 pop 到 A 控制器時,就會再次執行A控制器的 loadView 方法與 viewDidLoad 方法。

4.viewWillAppear && viewDidAppear

4.1 viewWillAppear

viewWillAppear 老是在 viewDidLoad 以後被調用,但不是當即,當你只是引用了屬性 view,卻沒有當即把 view 添加到任何已經展現的視圖上時,viewWillAppear 不會被調用,這在 view 被外部引用時,就會發生。固然,隨着 ViewController 的屢次推入,屢次進入子頁面後返回,該方法會被屢次調用。與 viewDidLoad 不一樣,調用該方法就說明控制器必定會顯示。

鎖屏以後會被調用嗎?

不會。viewWillAppear 關注的是 view 在層次中的顯示與消失,鎖屏並無改變 App 自己的層次。

Window疊加後,會被調用嗎?

不會。同鎖屏時的緣由相似,疊加 Window 並無改變 ViewController 所在 Window 的視圖層次,換句話說,view 並無被覆蓋或刪除 (相對於本身所在 Window)。

注意

若是控制器 A 被展現在另外一個控制器 B 的 popover 中,那麼控制器 B 不會調用該方法,直到控制器 A 清除。

4.2 viewDidAppear

視圖已在屏幕上渲染完成。子視圖有自定義動畫時,建議在 Did 方法中啓動,在 Will 中啓動動畫時,動畫效果將不會很理想。

5.viewWillAppear 與 viewDidAppear 之間發生了什麼

如下兩個方法將會被調用:

- viewWillLayoutSubviews
- viewDidLayoutSubviews
複製代碼
  • viewWillLayoutSubviews

    該方法在通知控制器將要佈局 view 的子控件時調用。每當視圖的 bounds 改變,view 將調整其子控件位置。默認實現爲空,可重寫以在 view 佈局子控件前作出改變。該方法調用時,AutoLayout 未起做用。

  • viewDidLayoutSubviews

    該方法在通知控制器已經佈局 view 的子控件時調用。默認實現爲空,可重寫以在 view 佈局子控件後作出改變。該方法調用時,AutoLayout 未起做用。

注意

使用 Autolayout 時,子視圖大小隻有在 viewDidLayoutSubviews 才真正被設置好,因此這裏纔是獲取子視圖大小的正確位置,常見的錯誤是,在 viewDidLoad 中讀取了某個 view.frame,用來給其它子視圖賦值,結果獲得一堆大小「不定」的視圖,甚至可能爲零,在視圖中看不見!

6.viewWillDisappear && viewDidDisappear

  • viewWillDisappear

    該方法在控制器 view 將要從視圖層次移除時被調用,可重寫以提交變動,取消視圖第一響應者狀態。

  • viewDidDisappear

    該方法在控制器 view 已經從視圖層次移除時被調用,可重寫以清除或隱藏控件。

二者配套調用,具體指子視圖控制器是以 push 和 present 方法顯示的,父視圖控制器的以上兩個方法會被觸發。

特別的,addSubview 會調用子控制器 Appear 系列方法,但不會調用父視圖 viewWillDisappear 方法。

以下添加子視圖:

XSDViewController *subVC = [[XSDViewController alloc] init];
[self addChildViewController:subVC];
[subVC.view setFrame:self.view.frame];
[self.view addSubview:subVC.view];
[subVC didMoveToParentViewController:self];
複製代碼

獲得結果是,只有 XSDViewController 的 Appear 系列方法被調用,這樣的調用與 push / present 方法根本不一樣是父視圖的 view 沒有「隱藏」,只是被覆蓋了。

7.didReceiveMemoryWarning && viewDidUnload (iOS6廢除)

當系統內存不足時,首先 ViewController 的 didReceiveMemoryWarining 方法會被調用,而 didReceiveMemoryWarining 會判斷當前 ViewController 的 view 是否顯示在 window 上,若是沒有顯示在 window 上,則 didReceiveMemoryWarining 會自動將 ViewController 的 view 以及其全部子 view 所有銷燬,而後調用 viewcontroller 的 viewdidunload 方法。若是當前 ViewController 的 view 顯示在 window 上,則不銷燬該 ViewController 的 view,固然,viewDidunload 也不會被調用了。

iOS 升級到 6.0 之後,再也不支持 viewDidUnload 了。官方文檔的解釋是系統會自動控制大的 view 所佔用的內存,其餘小的 view 所佔用的內存是極其微小的,不值得爲了省內存而去清理而後在從新建立。若是你須要在內存警告的時候釋放業務數據或者作些其餘的特定處理,你能夠實現 didReceiveMemoryWarning 這個函數。

iOS 6.0 及以上版本的內存警告處理方法:

-(void)didReceiveMemoryWarning {
	[super didReceiveMemoryWarning];//即便沒有顯示在window上,也不會自動的將self.view釋放。
 	// Dispose of any resources that can be recreated.
	// 此處作兼容處理須要加上ios6.0的宏開關,保證是在6.0下使用的,6.0之前屏蔽如下代碼,不然會在下面使用self.view時自動加載viewDidUnLoad
	if ([[UIDevice currentDevice].systemVersion floatValue] >= 6.0) {
		//須要注意的是self.isViewLoaded是必不可少的,其餘方式訪問視圖會致使它加載 ,在WWDC視頻也忽視這一點。
		if (self.isViewLoaded && !self.view.window) {// 是不是正在使用的視圖
			//code
			self.view = nil;// 目的是再次進入時可以從新加載調用viewDidLoad函數。
		}
	}
}
複製代碼

8.dealloc

當發出內存警告調用 viewDidUnload 方法時,只是釋放了 view,並無釋放 ViewController,因此並不會調用 dealloc 方法。即 viewDidUnload 和 dealloc 方法並無任何關係,dealloc 方法只會在 ViewController 被釋放的時候調用。

9.其餘相關方法

awakeFromNib

當 .nib 文件被加載的時候,會發送一個 awakeFromNib 的消息到 .nib 文件中的每一個對象,每一個對象均可以定義本身的 awakeFromNib 方法來響應這個消息,執行一些必要的操做。也就是說經過 nib 文件建立 view 對象時執行 awakeFromNib。

看完文檔繼續補充。

10.多個 ViewControllers 跳轉時的生命週期

10.1 Push / Present

當咱們點擊 push 的時候首先會加載下一個界面而後纔會調用界面的消失方法。

  • init:ViewController2
  • loadView:ViewController2
  • viewDidLoad:ViewController2
  • viewWillDisappear:ViewController1 將要消失
  • viewWillAppear:ViewController2 將要出現
  • viewWillLayoutSubviews ViewController2
  • viewDidLayoutSubviews ViewController2
  • viewWillLayoutSubviews:ViewController1
  • viewDidLayoutSubviews:ViewController1
  • viewDidDisappear:ViewController1 徹底消失
  • viewDidAppear:ViewController2 徹底出現

當在一個控制器內 Push / Present 新的控制器,原先的控制器並不會銷燬,但會消失,所以調用了 viewWillDisappear 和 viewDidDisappear 方法。

10.2 Pop / Dismiss

若是控制器 A 被展現在另外一個控制器 B 的 popover 中,那麼控制器 B 不會調用 viewWillAppear 方法,直到控制器 A 清除。這時,控制器 B 會再一次出現,所以調用了其中的 viewWillAppear 和 viewDidAppear 方法。

11.參考

相關文章
相關標籤/搜索