最近看了幾篇博客,在這里對(duì)ViewConroller的生命周期做一個(gè)總結(jié),抽絲剝繭吧,感覺(jué)有道理的拿出來(lái)匯總一下 。
一、結(jié)構(gòu)
按結(jié)構(gòu)可以對(duì)iOS的所有ViewController分成兩類:
1、主要用于展示內(nèi)容的ViewController,這種ViewController主要用于為用戶展示內(nèi)容,并與用戶交互,如UITableViewController,UIViewController。
2、用于控制和顯示其他ViewController的ViewController。這種ViewController一般都是一個(gè)ViewController的容器。如UINavigationController,UITabbarController。它們都有一個(gè)屬性:viewControllers。其中UINavigationController表示一種Stack式結(jié)構(gòu),push一個(gè)ViewController或pop一次,因此后一個(gè)ViewController一般會(huì)依賴前一個(gè)ViewController。而UITabbarController表示一個(gè)Array結(jié)構(gòu),各個(gè)ViewController是并列的。
第一種ViewController會(huì)經(jīng)常被繼承,用來(lái)顯示不同的數(shù)據(jù)給用戶。而第二種很少被繼承,除非你真的需要自定義它。
二、ViewController相關(guān)函數(shù)以及執(zhí)行順序
當(dāng)一個(gè)視圖控制器被創(chuàng)建,并在屏幕上顯示的時(shí)候。 代碼的執(zhí)行順序:**
1、alloc 創(chuàng)建對(duì)象,分配空間
2、init (initWithNibName) 初始化對(duì)象,初始化數(shù)據(jù)
3、loadView????????????? 從nib載入視圖 ,通常這一步不需要去干涉。除非你沒(méi)有使用xib文件創(chuàng)建視圖
4、viewDidLoad????????? 載入完成,可以進(jìn)行自定義數(shù)據(jù)以及動(dòng)態(tài)創(chuàng)建其他控件
5、viewWillAppear??????? 視圖將出現(xiàn)在屏幕之前,馬上這個(gè)視圖就會(huì)被展現(xiàn)在屏幕上了
6、viewDidAppear?????? ?視圖已在屏幕上渲染完成
當(dāng)一個(gè)視圖被移除屏幕并且銷毀的時(shí)候的執(zhí)行順序,這個(gè)順序差不多和上面的相反。**
1、viewWillDisappear????? 視圖將被從屏幕上移除之前執(zhí)行
2、viewDidDisappear???? 視圖已經(jīng)被從屏幕上移除,用戶看不到這個(gè)視圖了
3、dealloc?????????????? ?視圖被銷毀,此處需要對(duì)你在init和viewDidLoad中創(chuàng)建的對(duì)象進(jìn)行釋放
關(guān)于viewDidUnload
在發(fā)生內(nèi)存警告的時(shí)候如果本視圖不是當(dāng)前屏幕上正在顯示的視圖的話, viewDidUnload將會(huì)被執(zhí)行,本視圖的所有子視圖將被銷毀,以釋放內(nèi)存,此時(shí)開(kāi)發(fā)者需要手動(dòng)對(duì)viewLoad、viewDidLoad中創(chuàng)建的對(duì)象釋放內(nèi)存。 因?yàn)楫?dāng)這個(gè)視圖再次顯示在屏幕上的時(shí)候,viewLoad、viewDidLoad 再次被調(diào)用,以便再次構(gòu)造視圖。
三、Controller和View的生命周期
這里指的View是指Controller的View。它作為Controler的屬性,生命周期在Controller的生命周期內(nèi)。就是說(shuō)你的Controller不能在view釋放前就釋放了。
Controller和View的生命周期.png
當(dāng)你alloc并init了一個(gè)ViewController時(shí),這個(gè)ViewController應(yīng)該是還沒(méi)有創(chuàng)建view的。ViewController的view是使用了lazyInit方式創(chuàng)建,就是說(shuō)你調(diào)用的view屬性的getter:[self view]。在getter里會(huì)先判斷view是否創(chuàng)建,如果沒(méi)有創(chuàng)建,那么會(huì)調(diào)用loadView來(lái)創(chuàng)建view。loadView完成時(shí)會(huì)繼續(xù)調(diào)用viewDidLoad。loadView和viewDidLoad的一個(gè)區(qū)別就是:loadView時(shí)還沒(méi)有view。而viewDidLoad時(shí)view以及創(chuàng)建好了。
當(dāng)view被添加其他view中之前時(shí),會(huì)調(diào)用viewWillAppear,而之后會(huì)調(diào)用viewDidAppear。
當(dāng)view從其他view中移出之前時(shí),會(huì)調(diào)用viewWillDisAppear,而之后會(huì)調(diào)用viewDidDisappear。
當(dāng)view不在使用,而且是disappeared,受到內(nèi)存警告時(shí),那么viewController會(huì)將view釋放并將其指向nil。
四、App運(yùn)行時(shí)的調(diào)用順序
函數(shù)詳解
1)- (void)viewDidLoad;
一個(gè)APP在載入時(shí)會(huì)先通過(guò)調(diào)用loadView方法或者載入IB中創(chuàng)建的初始界面的方法,將視圖載入到內(nèi)存中。然后會(huì)調(diào)用viewDidLoad方法來(lái)進(jìn)行進(jìn)一步的設(shè)置。通常,我們對(duì)于各種初始數(shù)據(jù)的載入,初始設(shè)定等很多內(nèi)容,都會(huì)在這個(gè)方法中實(shí)現(xiàn),所以這個(gè)方法是一個(gè)很常用,很重要的方法。
但是要注意,這個(gè)方法只會(huì)在APP剛開(kāi)始加載的時(shí)候調(diào)用一次,以后都不會(huì)再調(diào)用它了,所以只能用來(lái)做初始設(shè)置。
2)- (void)viewDidUnload;
在內(nèi)存足夠的情況下,軟件的視圖通常會(huì)一直保存在內(nèi)存中,但是如果內(nèi)存不夠,一些沒(méi)有正在顯示的viewcontroller就會(huì)收到內(nèi)存不夠的警告,然后就會(huì)釋放自己擁有的視圖,以達(dá)到釋放內(nèi)存的目的。但是系統(tǒng)只會(huì)釋放內(nèi)存,并不會(huì)釋放對(duì)象的所有權(quán),所以通常我們需要在這里將不需要在內(nèi)存中保留的對(duì)象釋放所有權(quán),也就是將其指針置為nil。
這個(gè)方法通常并不會(huì)在視圖變換的時(shí)候被調(diào)用,而只會(huì)在系統(tǒng)退出或者收到內(nèi)存警告的時(shí)候才會(huì)被調(diào)用。但是由于我們需要保證在收到內(nèi)存警告的時(shí)候能夠?qū)ζ渥鞒龇磻?yīng),所以這個(gè)方法通常我們都需要去實(shí)現(xiàn)。
另外,即使在設(shè)備上按了Home鍵之后,系統(tǒng)也不一定會(huì)調(diào)用這個(gè)方法,因?yàn)镮OS4之后,系統(tǒng)允許將APP在后臺(tái)掛起,并將其繼續(xù)滯留在內(nèi)存中,因此,viewcontroller并不會(huì)調(diào)用這個(gè)方法來(lái)清除內(nèi)存。
3)- (void)viewWillAppear:(BOOL)animated;
系統(tǒng)在載入所有數(shù)據(jù)后,將會(huì)在屏幕上顯示視圖,這時(shí)會(huì)先調(diào)用這個(gè)方法。通常我們會(huì)利用這個(gè)方法,對(duì)即將顯示的視圖做進(jìn)一步的設(shè)置。例如,我們可以利用這個(gè)方法來(lái)設(shè)置設(shè)備不同方向時(shí)該如何顯示。
另外一方面,當(dāng)APP有多個(gè)視圖時(shí),在視圖間切換時(shí),并不會(huì)再次載入viewDidLoad方法,所以如果在調(diào)入視圖時(shí),需要對(duì)數(shù)據(jù)做更新,就只能在這個(gè)方法內(nèi)實(shí)現(xiàn)了。所以這個(gè)方法也非常常用。
4)- (void)viewDidAppear:(BOOL)animated;
有時(shí)候,由于一些特殊的原因,我們不能在viewWillApper方法里,對(duì)視圖進(jìn)行更新。那么可以重寫這個(gè)方法,在這里對(duì)正在顯示的視圖進(jìn)行進(jìn)一步的設(shè)置。
5)- (void)viewWillDisappear:(BOOL)animated;
在視圖變換時(shí),當(dāng)前視圖在即將被移除、或者被覆蓋時(shí),會(huì)調(diào)用這個(gè)方法進(jìn)行一些善后的處理和設(shè)置。
由于在IOS4之后,系統(tǒng)允許將APP在后臺(tái)掛起,所以在按了Home鍵之后,系統(tǒng)并不會(huì)調(diào)用這個(gè)方法,因?yàn)榫瓦@個(gè)APP本身而言,APP顯示的view,仍是掛起時(shí)候的view,所以并不會(huì)調(diào)用這個(gè)方法。
6)- (void)viewDidDisappear:(BOOL)animated;
我們可以重寫這個(gè)方法,對(duì)已經(jīng)消失,或者被覆蓋,或者已經(jīng)隱藏了的視圖做一些其他操作。
流程概述
運(yùn)行APP —> 載入視圖 —> 調(diào)用viewDidLoad方法 —> 調(diào)用viewWillAppear方法 —> viewWillLayoutSubviews —> viewDidLayoutSubviews—> 調(diào)用viewDidAppear方法 —> 正常運(yùn)行
APP需要調(diào)用另一個(gè)view—> 調(diào)用viewWillDisappear—>調(diào)用viewDidDisappear—>收到內(nèi)存警告didReceiveMemoryWarning —>釋放對(duì)象所有權(quán)delloc
流程圖
五、注意
1、init里不要出現(xiàn)創(chuàng)建view的代碼。良好的設(shè)計(jì),在init里應(yīng)該只有相關(guān)數(shù)據(jù)的初始化,而且這些數(shù)據(jù)都是比較關(guān)鍵的數(shù)據(jù)。init里不要掉self.view,否則會(huì)導(dǎo)致viewcontroller創(chuàng)建view。(因?yàn)関iew是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方法,但建議不要重載這個(gè)方法。
3、viewDidLoad 這時(shí)候view已經(jīng)有了,最適合創(chuàng)建一些附加的view和控件了。有一點(diǎn)需要注意的是,viewDidLoad會(huì)調(diào)用多次(viewcontroller可能多次載入view,參見(jiàn)圖2)。
4、viewWillAppear 這個(gè)一般在view被添加到superview之前,切換動(dòng)畫之前調(diào)用。在這里可以進(jìn)行一些顯示前的處理。比如鍵盤彈出,一些特殊的過(guò)程動(dòng)畫(比如狀態(tài)條和navigationbar顏色)。
5、viewDidAppear 一般用于顯示后,在切換動(dòng)畫后,如果有需要的操作,可以在這里加入相關(guān)代碼。
6、viewDidUnload 這時(shí)候viewController的view已經(jīng)是nil了。由于這一般發(fā)生在內(nèi)存警告時(shí),所以在這里你應(yīng)該將那些不在顯示的view釋放了。比如你在viewcontroller的view上加了一個(gè)label,而且這個(gè)label是viewcontroller的屬性,那么你要把這個(gè)屬性設(shè)置成nil,以免占用不必要的內(nèi)存,而這個(gè)label在viewDidLoad時(shí)會(huì)重新創(chuàng)建。
7、從nib文件加載視圖的controller,只要不釋放,在每次viewWillAppear時(shí)都會(huì)調(diào)用layoutSubviews方法,有時(shí)甚至?xí)趘iewDidAppear后在調(diào)用一次layoutSubviews,而從代碼加載視圖的則只會(huì)在開(kāi)始調(diào)用一次,之后都不會(huì),所以注意,在layoutSubviews中寫相關(guān)的布局代碼十分危險(xiǎn)。
六、代碼示例
#pragma mark --- life circle
// 非storyBoard(xib或非xib)都走這個(gè)方法
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
NSLog(@"%s", __FUNCTION__);
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
}
return self;
}
// 如果連接了串聯(lián)圖storyBoard 走這個(gè)方法
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
NSLog(@"%s", __FUNCTION__);
if (self = [super initWithCoder:aDecoder]) {
}
return self;
}
// 加載視圖(默認(rèn)從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];
}
//視圖將要出現(xiàn)
- (void)viewWillAppear:(BOOL)animated {
NSLog(@"%s", __FUNCTION__);
[super viewWillAppear:animated];
}
// view 即將布局其 Subviews
- (void)viewWillLayoutSubviews {
NSLog(@"%s", __FUNCTION__);
[super viewWillLayoutSubviews];
}
// view 已經(jīng)布局其 Subviews
- (void)viewDidLayoutSubviews {
NSLog(@"%s", __FUNCTION__);
[super viewDidLayoutSubviews];
}
//視圖已經(jīng)出現(xiàn)
- (void)viewDidAppear:(BOOL)animated {
NSLog(@"%s", __FUNCTION__);
[super viewDidAppear:animated];
}
//視圖將要消失
- (void)viewWillDisappear:(BOOL)animated {
NSLog(@"%s", __FUNCTION__);
[super viewWillDisappear:animated];
}
//視圖已經(jīng)消失
- (void)viewDidDisappear:(BOOL)animated {
NSLog(@"%s", __FUNCTION__);
[super viewDidDisappear:animated];
}
//出現(xiàn)內(nèi)存警告 //模擬內(nèi)存警告:點(diǎn)擊模擬器->hardware-> Simulate Memory Warning
- (void)didReceiveMemoryWarning {
NSLog(@"%s", __FUNCTION__);
[super didReceiveMemoryWarning];
}
// 視圖被銷毀
- (void)dealloc {
NSLog(@"%s", __FUNCTION__);
}
查看 打印 結(jié)果
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:]
分析
- initWithNibName:bundle:
初始化UIViewController,執(zhí)行關(guān)鍵數(shù)據(jù)初始化操作,非StoryBoard創(chuàng)建UIViewController都會(huì)調(diào)用這個(gè)方法。
注意: 不要在這里做View相關(guān)操作,View在loadView方法中才初始化。
- initWithCoder:
如果使用StoryBoard進(jìn)行視圖管理,程序不會(huì)直接初始化一個(gè)UIViewController,StoryBoard會(huì)自動(dòng)初始化或在segue被觸發(fā)時(shí)自動(dòng)初始化,因此方法initWithNibName:bundle不會(huì)被調(diào)用,但是initWithCoder會(huì)被調(diào)用。
- loadView
當(dāng)訪問(wèn)UIViewController的View屬性時(shí),View如果此時(shí)為nil,那么ViewController會(huì)自動(dòng)調(diào)用loadView方法來(lái)初始化一個(gè)UIView并賦值給UIViewController的View;如果沒(méi)有重載lodaView方法,則UIViewController會(huì)從nib或StoryBoard中查找默認(rèn)的loadView,默認(rèn)的loadView會(huì)返回一個(gè)空白的UIView對(duì)象。
注意:在view初始化之前,不能先調(diào)用view的getter方法,否則將導(dǎo)致死循環(huán)(除非先調(diào)用[super loadView])
- viewDidLoad
當(dāng)loadView將view載入內(nèi)存中,會(huì)進(jìn)一步調(diào)用viewDidLoad方法來(lái)進(jìn)行進(jìn)一步設(shè)置。通常,我們對(duì)于各種初始化數(shù)據(jù)的載入,初始設(shè)定等很多內(nèi)容都會(huì)在這個(gè)方法中實(shí)現(xiàn)。
- viewWillAppear
系統(tǒng)在載入所有的數(shù)據(jù)后,將會(huì)在屏幕上顯示視圖,這時(shí)會(huì)先調(diào)用這個(gè)方法,通常我們會(huì)在這個(gè)方法對(duì)即將顯示的視圖做進(jìn)一步的設(shè)置。比如,設(shè)置設(shè)備不同方向時(shí)該如何顯示;設(shè)置狀態(tài)欄方向、設(shè)置視圖顯示樣式等。
另一方面,當(dāng)APP有多個(gè)視圖時(shí),上下級(jí)視圖切換是也會(huì)調(diào)用這個(gè)方法,如果在調(diào)入視圖時(shí),需要對(duì)數(shù)據(jù)做更新,就只能在這個(gè)方法內(nèi)實(shí)現(xiàn)。
- viewWillLayoutSubviews
view 即將布局其Subviews。 比如view的bounds改變了(例如:狀態(tài)欄從不顯示到顯示,視圖方向變化),要調(diào)整Subviews的位置,在調(diào)整之前要做的工作可以放在該方法中實(shí)現(xiàn)
- viewDidLayoutSubviews
view已經(jīng)布局其Subviews,這里可以放置調(diào)整完成之后需要做的工作。
- viewDidAppear
在view被添加到視圖層級(jí)中以及多視圖,上下級(jí)視圖切換時(shí)調(diào)用這個(gè)方法,在這里可以對(duì)正在顯示的視圖做進(jìn)一步的設(shè)置。
- viewWillDisappear
在視圖切換是,當(dāng)前視圖在即將被移除、或被覆蓋是,會(huì)調(diào)用該方法,此時(shí)還沒(méi)有調(diào)用removeFromSuperview。
- viewDidDisappear
view已經(jīng)消失或被覆蓋,此時(shí)已經(jīng)調(diào)用removeFromSuperView;
- dealloc
視圖被銷毀,此次需要對(duì)你在init和viewDidLoad中創(chuàng)建的對(duì)象進(jìn)行釋放。
- didReceiveMemoryWarning
在內(nèi)存足夠的情況下,app的視圖通常會(huì)一直保存在內(nèi)存中,但是如果內(nèi)存不夠,一些沒(méi)有正在顯示的viewController就會(huì)收到內(nèi)存不夠的警告,然后就會(huì)釋放自己擁有的視圖,以達(dá)到釋放內(nèi)存的目的。但是系統(tǒng)只會(huì)釋放內(nèi)存,并不會(huì)釋放對(duì)象的所有權(quán),所以通常我們需要在這里將不需要顯示在內(nèi)存中保留的對(duì)象釋放它的所有權(quán),將其指針置nil。
------整理