1. 程式人生 > >ViewController的生命週期分析和使用

ViewController的生命週期分析和使用

iOS的SDK中提供很多原生ViewController,大大提高了我們的開發效率,下面是我的一些經驗。

一、結構

按結構可以對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會經常被繼承,用來顯示不同的資料給使用者。而第二種很少被繼承,除非你真的需要自定義它。 注:細心的同學應該能發現,在Xcode中新建一個ViewController時,只可以選擇繼承自UIViewController和UITableViewController,而它們都是第一種。

圖1

二、Controller和View的生命週期

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

圖2 ViewController生命週期

當你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。

三、程式碼組織(如何設計良好的viewcontroller)

ViewController生命週期中有那麼多函式,一個重要問題就是什麼程式碼該寫在什麼地方。 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時會重新建立。

在我之前的學習筆記中討論過ViewController,過了這麼久,對它也有了新的認識和體會,ViewController是我們在開發過程中碰到最多的朋友,今天就來好好認識一下它。ViewController是IOS開發中MVC模式中的C,ViewController是view的controller,ViewController的職責主要包括管理內部各個view的載入顯示和解除安裝,同時負責與其他ViewController的通訊和協調。在IOS中,有兩類ViewController,一類是顯示內容的,比如UIViewController、UITableViewController等,同時還可以自定義繼承自UIViewController的ViewController;另一類是ViewController容器,UINavigationViewController和UITabBarController等,UINavigationController是以Stack的形式來儲存和管理ViewController,UITabBarController是以Array的形式來管理ViewController。和Android中Activity一樣,IOS開發中,ViewController也有自己的生命週期(Lifecycle)。

首先來看看View的載入過程,如下圖:

從圖中可以看到,在view載入過程中首先會呼叫loadView方法,在這個方法中主要完成一些關鍵view的初始化工作,比如UINavigationViewController和UITabBarController等容器類的ViewController;接下來就是載入view,載入成功後,會接著呼叫viewDidLoad方法,這裡要記住的一點是,在loadView之前,是沒有view的,也就是說,在這之前,view還沒有被初始化。完成viewDidLoad方法後,ViewController裡面就成功的載入view了,如上圖右下角所示。

在Controller中建立view有兩種方式,一種是通過程式碼建立、一種是通過Storyboard或Interface Builder來建立,後者可以比較直觀的配置view的外觀和屬性,Storyboard配合IOS6後推出的AutoLayout,應該是Apple之後主推的一種UI定製解決方案,後期我會專門介紹一篇使用AutoLayout進行UI製作的文章。言歸正傳,通過IB或Storyboard建立view,在Controller中建立view後,會在Controller中對view進行一些操作,會出現如下程式碼:

  1. @interface MyViewController()  
  2. @property (nonatomic) IBOutlet id myButton;  
  3. @property (nonatomic) IBOutlet id myTextField;  
  4. - (IBAction)myAction:(id)sender;  
  5. @end  

這裡用IBOutlet標記了一個UIButton和一個UITextField,用IBAction來標記UIButton的響應事件,IBOutlet和IBAction都是一個整形常量,用來標記控制元件,通過一張圖能比較清晰的看清他們之間的關係:

上圖中,MyViewController是繼承自UIViewController的一個自定義ViewController,它包含兩個View,一個是UIButton,一個是UITextField,從箭頭的指向性上就可以比較好的理解IBOutlet和IBAction了。IBOutlet是告訴Interface Builder,此例項變數被連線到nib檔案中的view物件,IBOutlet本身不做任何操作,只是一個標記作用。IBAction同樣是個標記關鍵字,它只能標記方法,它告訴IB用IBAction標記的方法可以被某個控制元件觸發。

通過程式設計的方式建立view,如下程式碼:

  1. - (void)loadView  
  2. {  
  3.     CGRect applicationFrame = [[UIScreen mainScreen] applicationFrame];  
  4.     UIView *contentView = [[UIView alloc] initWithFrame:applicationFrame];  
  5.     contentView.backgroundColor = [UIColor blackColor];  
  6.     self.view = contentView;  
  7.     levelView = [[LevelView alloc] initWithFrame:applicationFrame viewController:self];  
  8.     [self.view addSubview:levelView];  
  9. }  

上述程式碼首先得到螢幕的frame,然後根據該frame生成了一個contentView,並指定當前ViewController的root view為contentView,然後生成了一個LevelView的自定義View並將它通過addSubview:方法新增到當前ViewController當中,完成view的初始化載入。

關於loadView方法的重寫,官方文件中有一個明顯的註釋,原文如下:

Note: When overriding the loadView method to create your views programmatically, you should not call super. Doing so initiates the default view-loading behavior and usually just wastes CPU cycles. Your own implementation of the loadView method should do all the work that is needed to create a root view and subviews for your view controller.

意思是當通過程式碼方式去建立你自己的view時,在loadView方法中不應該呼叫super,如果呼叫[super loadView]會影響CPU效能。  

接下來我們看看ViewController中的view是如何被解除安裝的:

從圖中可以看到,當系統發出記憶體警告時,會呼叫didReceiveMemoeryWarning方法,如果當前有能被釋放的view,系統會呼叫viewWillUnload方法來釋放view,完成後呼叫viewDidUnload方法,至此,view就被解除安裝了。此時原本指向view的變數要被置為nil,具體操作是在viewDidUnload方法中呼叫self.myButton = nil;

小結一下:

loadView和viewDidLoad的區別就是,loadView時view還沒有生成,viewDidLoad時,view已經生成了,loadView只會被呼叫一次,而viewDidLoad可能會被呼叫多次(View可能會被多次載入),當view被新增到其他view中之前,會呼叫viewWillAppear,之後會呼叫viewDidAppear。當view從其他view中移除之前,呼叫viewWillDisAppear,移除之後會呼叫viewDidDisappear。當view不再使用時,受到記憶體警告時,ViewController會將view釋放並將其指向為nil。

ViewController的生命週期中各方法執行流程如下:

init—>loadView—>viewDidLoad—>viewWillApper—>viewDidApper—>viewWillDisapper—>viewDidDisapper—>viewWillUnload->viewDidUnload—>dealloc