iOS的SDK中提供很多原生ViewController,大大提高了我們的開發(fā)效率,下面是我的一些經(jīng)驗。
<a name="t0" style="margin: 0px; padding: 0px;"></a>一、結(jié)構(gòu)
按結(jié)構(gòu)可以對iOS的所有ViewController分成兩類:
1、主要用于展示內(nèi)容的ViewController,這種ViewController主要用于為用戶展示內(nèi)容,并與用戶交互,如UITableViewController,UIViewController。
2、用于控制和顯示其他ViewController的ViewController。這種ViewController一般都是一個ViewController的容器。如UINavigationController,UITabbarController。它們都有一個屬性:viewControllers。其中UINavigationController表示一種Stack式結(jié)構(gòu),push一個ViewController或pop一次,因此后一個ViewController一般會依賴前一個ViewController。而UITabbarController表示一個Array結(jié)構(gòu),各個ViewController是并列的。
第一種ViewController會經(jīng)常被繼承,用來顯示不同的數(shù)據(jù)給用戶。而第二種很少被繼承,除非你真的需要自定義它。
注:細心的同學(xué)應(yīng)該能發(fā)現(xiàn),在Xcode中新建一個ViewController時,只可以選擇繼承自UIViewController和UITableViewController,而它們都是第一種。
<a name="t1" style="margin: 0px; padding: 0px;"></a>二、Controller和View的生命周期
這里指的View是指Controller的View。它作為Controler的屬性,生命周期在Controller的生命周期內(nèi)。就是說你的Controller不能在view釋放前就釋放了。
圖2 ViewController生命周期
當(dāng)你alloc并init了一個ViewController時,這個ViewController應(yīng)該是還沒有創(chuàng)建view的。ViewController的view是使用了lazyInit方式創(chuàng)建,就是說你調(diào)用的view屬性的getter:[self view]。在getter里會先判斷view是否創(chuàng)建,如果沒有創(chuàng)建,那么會調(diào)用loadView來創(chuàng)建view。loadView完成時會繼續(xù)調(diào)用viewDidLoad。loadView和viewDidLoad的一個區(qū)別就是:loadView時還沒有view。而viewDidLoad時view以及創(chuàng)建好了。
當(dāng)view被添加其他view中之前時,會調(diào)用viewWillAppear,而之后會調(diào)用viewDidAppear。
當(dāng)view從其他view中移出之前時,會調(diào)用viewWillDisAppear,而之后會調(diào)用viewDidDisappear。
當(dāng)view不在使用,而且是disappeared,受到內(nèi)存警告時,那么viewController會將view釋放并將其指向nil。
<a name="t2" style="margin: 0px; padding: 0px;"></a>三、代碼組織(如何設(shè)計良好的viewcontroller)
ViewController生命周期中有那么多函數(shù),一個重要問題就是什么代碼該寫在什么地方。
1、init里不要出現(xiàn)創(chuàng)建view的代碼。良好的設(shè)計,在init里應(yīng)該只有相關(guān)數(shù)據(jù)的初始化,而且這些數(shù)據(jù)都是比較關(guān)鍵的數(shù)據(jù)。init里不要掉self.view,否則會導(dǎo)致viewcontroller創(chuàng)建view。(因為view是lazyinit的)。
2、loadView中只初始化view,一般用于創(chuàng)建比較關(guān)鍵的view如tableViewController的tabView,UINavigationController的navgationBar,不可掉用view的getter(在掉super loadView前),最好也不要初始化一些非關(guān)鍵的view。如果你是從nib文件中創(chuàng)建的viewController在這里一定要首先調(diào)用super的loadView方法,但建議不要重載這個方法。
3、viewDidLoad 這時候view已經(jīng)有了,最適合創(chuàng)建一些附加的view和控件了。有一點需要注意的是,viewDidLoad會調(diào)用多次(viewcontroller可能多次載入view,參見圖2)。
4、viewWillAppear 這個一般在view被添加到superview之前,切換動畫之前調(diào)用。在這里可以進行一些顯示前的處理。比如鍵盤彈出,一些特殊的過程動畫(比如狀態(tài)條和navigationbar顏色)。
5、viewDidAppear 一般用于顯示后,在切換動畫后,如果有需要的操作,可以在這里加入相關(guān)代碼。
6、viewDidUnload 這時候viewController的view已經(jīng)是nil了。由于這一般發(fā)生在內(nèi)存警告時,所以在這里你應(yīng)該將那些不在顯示的view釋放了。比如你在viewcontroller的view上加了一個label,而且這個label是viewcontroller的屬性,那么你要把這個屬性設(shè)置成nil,以免占用不必要的內(nèi)存,而這個label在viewDidLoad時會重新創(chuàng)建。
在我之前的學(xué)習(xí)筆記中討論過ViewController,過了這么久,對它也有了新的認識和體會,ViewController是我們在開發(fā)過程中碰到最多的朋友,今天就來好好認識一下它。ViewController是IOS開發(fā)中MVC模式中的C,ViewController是view的controller,ViewController的職責(zé)主要包括管理內(nèi)部各個view的加載顯示和卸載,同時負責(zé)與其他ViewController的通信和協(xié)調(diào)。在IOS中,有兩類ViewController,一類是顯示內(nèi)容的,比如UIViewController、UITableViewController等,同時還可以自定義繼承自UIViewController的ViewController;另一類是ViewController容器,UINavigationViewController和UITabBarController等,UINavigationController是以Stack的形式來存儲和管理ViewController,UITabBarController是以Array的形式來管理ViewController。和Android中Activity一樣,IOS開發(fā)中,ViewController也有自己的生命周期(Lifecycle)。
首先來看看View的加載過程,如下圖:
[圖片上傳失敗...(image-a4e2d8-1510536625033)]
從圖中可以看到,在view加載過程中首先會調(diào)用loadView方法,在這個方法中主要完成一些關(guān)鍵view的初始化工作,比如UINavigationViewController和UITabBarController等容器類的ViewController;接下來就是加載view,加載成功后,會接著調(diào)用viewDidLoad方法,這里要記住的一點是,在loadView之前,是沒有view的,也就是說,在這之前,view還沒有被初始化。完成viewDidLoad方法后,ViewController里面就成功的加載view了,如上圖右下角所示。
在Controller中創(chuàng)建view有兩種方式,一種是通過代碼創(chuàng)建、一種是通過Storyboard或Interface Builder來創(chuàng)建,后者可以比較直觀的配置view的外觀和屬性,Storyboard配合IOS6后推出的AutoLayout,應(yīng)該是Apple之后主推的一種UI定制解決方案,后期我會專門介紹一篇使用AutoLayout進行UI制作的文章。言歸正傳,通過IB或Storyboard創(chuàng)建view,在Controller中創(chuàng)建view后,會在Controller中對view進行一些操作,會出現(xiàn)如下代碼:
[cpp] view plain
@interface MyViewController()
@property (nonatomic) IBOutlet id myButton;
@property (nonatomic) IBOutlet id myTextField;
- (IBAction)myAction:(id)sender;
@end
這里用IBOutlet標記了一個UIButton和一個UITextField,用IBAction來標記UIButton的響應(yīng)事件,IBOutlet和IBAction都是一個整形常量,用來標記控件,通過一張圖能比較清晰的看清他們之間的關(guān)系:
上圖中,MyViewController是繼承自UIViewController的一個自定義ViewController,它包含兩個View,一個是UIButton,一個是UITextField,從箭頭的指向性上就可以比較好的理解IBOutlet和IBAction了。IBOutlet是告訴Interface Builder,此實例變量被連接到nib文件中的view對象,IBOutlet本身不做任何操作,只是一個標記作用。IBAction同樣是個標記關(guān)鍵字,它只能標記方法,它告訴IB用IBAction標記的方法可以被某個控件觸發(fā)。
通過編程的方式創(chuàng)建view,如下代碼:
[cpp] view plain
-
- (
void)loadView
{
CGRect applicationFrame = [[UIScreen mainScreen] applicationFrame];
UIView *contentView = [[UIView alloc] initWithFrame:applicationFrame];
contentView.backgroundColor = [UIColor blackColor];
self.view = contentView;
levelView = [[LevelView alloc] initWithFrame:applicationFrame viewController:self];
[self.view addSubview:levelView];
}
上述代碼首先得到屏幕的frame,然后根據(jù)該frame生成了一個contentView,并指定當(dāng)前ViewController的root view為contentView,然后生成了一個LevelView的自定義View并將它通過addSubview:方法添加到當(dāng)前ViewController當(dāng)中,完成view的初始化加載。
關(guān)于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.
意思是當(dāng)通過代碼方式去創(chuàng)建你自己的view時,在loadView方法中不應(yīng)該調(diào)用super,如果調(diào)用[super loadView]會影響CPU性能。
接下來我們看看ViewController中的view是如何被卸載的:
[圖片上傳失敗...(image-3a66dd-1510536625033)]
從圖中可以看到,當(dāng)系統(tǒng)發(fā)出內(nèi)存警告時,會調(diào)用didReceiveMemoeryWarning方法,如果當(dāng)前有能被釋放的view,系統(tǒng)會調(diào)用viewWillUnload方法來釋放view,完成后調(diào)用viewDidUnload方法,至此,view就被卸載了。此時原本指向view的變量要被置為nil,具體操作是在viewDidUnload方法中調(diào)用self.myButton = nil;
小結(jié)一下:
loadView和viewDidLoad的區(qū)別就是,loadView時view還沒有生成,viewDidLoad時,view已經(jīng)生成了,loadView只會被調(diào)用一次,而viewDidLoad可能會被調(diào)用多次(View可能會被多次加載),當(dāng)view被添加到其他view中之前,會調(diào)用viewWillAppear,之后會調(diào)用viewDidAppear。當(dāng)view從其他view中移除之前,調(diào)用viewWillDisAppear,移除之后會調(diào)用viewDidDisappear。當(dāng)view不再使用時,受到內(nèi)存警告時,ViewController會將view釋放并將其指向為nil。
ViewController的生命周期中各方法執(zhí)行流程如下:
init—>loadView—>viewDidLoad—>viewWillApper—>viewDidApper—>viewWillDisapper—>viewDidDisapper—>viewWillUnload->viewDidUnload—>dealloc