UIViewController 的生命周期
答:來源:http://www.cnblogs.com/dahe007/p/6002964.html
一、下面帶 (NSObject)的方法是NSObject提供的方法。其他的都是UIViewController 提供的方法。
load (NSObject)
initialize (NSObject)
init (NSObject)
initWithCoder
initWithNibName
awakeFromNib (NSObject)
loadView
viewDidLoad
viewWillAppear
updateViewConstraints
viewWillLayoutSubviews
viewDidLayoutSubviews
viewDidAppear
viewWillDisappear
viewDidDisappear
dealloc (NSObject)
didReceiveMemoryWarning
二、load、initialize、init
load、initialize是繼承的NSObject的方法。這些在其他文章里寫過,就不詳述了。大體說下。
在main方法還沒執(zhí)行的時候 就會 加載所有類,調(diào)用所有類的load方法。
一般會在load中實現(xiàn)Method Swizzle。
初始化對象,調(diào)用alloc的時候會調(diào)用initialize方法。這個時候是分配內(nèi)存。
init方法是在內(nèi)存中創(chuàng)建好對象。
三、 initWithCoder、initWithNibName、awakeFromNib
1、initWithCoder、awakeFromNib
initWithCoder:反歸檔,如果對象是從文件解析來的 就會調(diào)用。
awakeFromNib: 從xib或者storyboard加載完畢 會調(diào)用。
新建UIView的子類并且想在load nib的時候做一些初始化工作的時候 可以重寫awakeFromNib。bundle在load nib后會給每個view對象發(fā)送一個awakeFromNib消息。
2、用storyboard,順序:
initialize -> initWithCoder -> awakeFromNib -> loadView
2、用Xib或者純代碼:
如果用[[VC alloc] init] 來初始化:
initialize -> init -> initWithNibName -> loadView
如果用[[VC alloc] initWithNibName:@“VC” bundle:nil] 來初始化:
initialize -> initWithNibName -> loadView
使用Xib來實現(xiàn)VC的時候,不要重寫loadView方法。如果重寫了loadView方法,則Xib中View就會消失,變成空View。
四、loadView
1、要重新設(shè)置View的時候,在loadView中去創(chuàng)建UIViewController的view,也就是self.view。
在這個方法中主要完成一些關(guān)鍵view的初始化工作。加載成功后接著調(diào)用viewDidLoad方法。
在loadView之前,self.view是不存在的,也就是說view還沒有被初始化,loadView完成后view就建立好了。
比如:
- (void)loadView {
NSLog(@"loadView中調(diào)用self.view : %@", self.view);
}
這樣會直接崩潰,因為self.view 還沒創(chuàng)建。
在重寫的loadView中調(diào)用[super loadView],會自動初始化self.view。如下方式就不會崩潰:
- (void)loadView {
[super loadView];
NSLog(@"loadView中調(diào)用self.view : %@", self.view);
}
2、每次訪問UIViewController的view(比如vc.view、self.view)而且view為nil,loadView方法就會被調(diào)用。
- (void)loadView {
[super loadView];
NSLog(@"loadView中調(diào)用self.view : %@", self.view);
}
- (void)viewDidLoad {
[super viewDidLoad];
self.view = nil;
self.view.backgroundColor = [UIColor redColor];
//這時候view為nil,會再次調(diào)用loadView方法。執(zhí)行完 loadView后繼續(xù)執(zhí)行viewDidLoad方法,這樣就死循環(huán)了。
}
上面代碼 直接就死循環(huán)了,一直執(zhí)行l(wèi)oadView和viewDidLoad方法。
3、loadView中自定義了view,那么xib、storyboard中設(shè)置的頁面就會失效,frame也是無效的。如:
- (void)loadView {
UIView *customView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
customView.backgroundColor = [UIColor redColor];
self.view = customView;
}
上面代碼執(zhí)行的效果是 全屏都是紅色。
4、修改self.view的大小,可以在 viewDidLayoutSubviews 方法里去修改:
- (void)viewWillLayoutSubviews {
self.view.frame = CGRectMake(0, 0, 150, 150);
}
5、默認(rèn)的[super loadView] 中做了哪些事情:
5.1、如果UIViewController制定xib的名字或者storyboard 進(jìn)行初始化 ,這樣就會根據(jù)傳入的xib的名字 去初始化view。
5.2、如果xib沒有顯示的指定名稱,就默認(rèn)加載和UIViewController同名的xib文件。
5.3、如果沒有找到xib文件就會創(chuàng)建 一個空白的UIView,這個view的frame為屏幕的大小。
五、viewDidLoad
loadView執(zhí)行完成后調(diào)用viewDidLoad,viewDidLoad中主要完成界面的初始化 。如:往view上添加子視圖、讀取數(shù)據(jù)等。
頁面打開后,如果沒有銷毀 就只執(zhí)行一次。
六、viewWillAppear
viewDidLoad執(zhí)行完成后就執(zhí)行viewWillAppear。每次打開頁面都會執(zhí)行viewWillAppear。比如:從A頁面push到B頁面,然后從B頁面返回到A頁面的時候,viewDidLoad就不再執(zhí)行,而viewWillAppear還是會執(zhí)行。
如果要求每次顯示該頁面的時候都要刷新網(wǎng)絡(luò)數(shù)據(jù),就可以在viewWillAppear做網(wǎng)絡(luò)請求的操作。
七、updateViewConstraints
主要功能是更新view的約束,并會調(diào)用其所有子視圖的該方法去更新約束。
ViewController的View在更新視圖布局時,會先調(diào)用ViewController的updateViewConstraints 方法。我們可以通過重寫這個方法去更新當(dāng)前View的內(nèi)部布局,而不用再繼承這個View去重寫-updateConstraints方法。
兩個方法都需要在方法實現(xiàn)的最后調(diào)用父類的該方法。并且這兩個方法不建議直接調(diào)用。
- (void)updateViewConstraints {
//在這里給view添加約束,請確保該view的translatesAutoresizingMaskIntoConstraints屬性已設(shè)置為false
[super updateViewConstraints];
}
八、viewWillLayoutSubviews
view即將布局其Subviews。比如view的bounds改變了,要調(diào)整Subviews的位置,在調(diào)整之前要做的一些工作就可以在該方法中實現(xiàn)。
我們可以在這里設(shè)置 subviews 的 frame 屬性。
在 Autolayout 機制被調(diào)用之前,viewWillLayoutSubviews 會被調(diào)用,在 Autolayout 機制被調(diào)用之后,viewDidLayoutSubviews 會被調(diào)用。即在 viewWillLayoutSubviews 和 viewDidLayoutSubviews 之間,Autolayout 機制會被調(diào)用。所以從viewWillLayoutSubviews 中獲取的frame 是老的frame,從viewDidLayoutSubviews獲取的frame才是正確的frame。
比如xib中設(shè)置了約束,然后 在viewDidLoad中設(shè)置frame,真實的位置還是根據(jù)xib中的約束顯示的。
而且在viewWillLayoutSubviews中修改frame也是不生效的,那么,如何才能用代碼修改布局生效?
方法一:
在viewDidLayoutSubviews中修改frame,這是最簡單的方法。
方法二:推薦做法
在updateConstraints中修改約束。
方法三:
在viewWillLayoutSubviews中修改約束
九、viewDidLayoutSubviews
控制器的view布局子控件完成。這里獲取的frame才是最正確的frame。
如果控件用的約束來布局的,在viewDidLayoutSubviews 中 去設(shè)置視圖的frame 是無效的。
如果控件是用frame來布局的,在viewDidLayoutSubviews 中 去設(shè)置視圖的frame 是有效的。
self.view 在viewDidLayoutSubviews 中 去設(shè)置 frame 是有效的。
十、viewWillDisappear
控制器的view即將消失的時候
十一、viewDidDisappear
控制器的view完全消失的時候
十二、 dealloc
當(dāng)對象被釋放的時候調(diào)用dealloc。
ARC中dealloc方法中常用的一些作用:
在dealloc中移除掉notification,解除delegate關(guān)系。
看一個類中是否有循環(huán)引用,可以用Instrument或者FBMemoryProfiler,不過最簡單的還是在dealloc里打印日志。
十三、didReceiveMemoryWarning
當(dāng)系統(tǒng)內(nèi)存不足時,VC的didReceiveMemoryWarining 方法會被調(diào)用,VC所在的導(dǎo)航棧(self.navigationController.viewControllers)中的VC 也會被調(diào)didReceiveMemoryWarining。
didReceiveMemoryWarining 會判斷當(dāng)前VC的view是否顯示在window上,
如果沒有顯示在window上,則didReceiveMemoryWarining 會自動將VC 的view以及其所有子view全部銷毀。
如果當(dāng)前UIViewController的view顯示在window上,則不銷毀該viewcontroller的view。
AppStore中一些可以釋放內(nèi)存的APP,就是先不斷增加使用的內(nèi)存,然后系統(tǒng)檢測到內(nèi)存不足的時候 會自動進(jìn)行回收。
擴展:http://www.itdecent.cn/p/a5f82922e387
loadView方法
當(dāng)我們用到控制器view時,就會調(diào)用控制器view的get方法,在get方法內(nèi)部,首先判斷view是否已經(jīng)創(chuàng)建,如果已存在,則直接返回存在的view,如果不存在,則調(diào)用控制器的loadView方法,在控制器沒有被銷毀的情況下,loadView也可能會被執(zhí)行多次
viewDidLoad方法
當(dāng)控制器的loadView方法執(zhí)行完畢,view被創(chuàng)建成功后,就會執(zhí)行viewDidLoad方法,該方法與loadView方法一樣,也有可能被執(zhí)行多次。在開發(fā)中,我們可能從未遇到過執(zhí)行多次的情況,那什么時候會執(zhí)行多次呢?
比如A控制器push出B控制器,此時,窗口顯示的是B控制器的view,此時如果收到內(nèi)存警告,我們一般會將A控制器中沒用的變量及view銷毀掉,之后當(dāng)我們從B控制器pop到A控制器時,就會再次執(zhí)行A控制器的loadView方法與viewDidLoad方法。
控制器view的加載
1.通過storyboard加載
當(dāng)控制器通過storyboard加載時,需要指定storyboard的名稱,控制器view最終就是storyboard所描述的樣子。
- (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];
}
2.通過xib加載當(dāng)控制器view通過xib加載的時候,可能會出現(xiàn)三種情況
a. 指定xib名稱(OtherViewController.xib)
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
TestViewController *testVC = [[TestViewController alloc] initWithNibName:@"OtherViewController" bundle:nil];
[self.navigationController pushViewController:testVC animated:YES];
}
當(dāng)我們指定了xib的名稱,loadView方法就會去加載對應(yīng)的xib(OtherViewController.xib)
b.不指定xib名稱1
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
TestViewController *testVC = [[TestViewController alloc] init];
[self.navigationController pushViewController:testVC animated:YES];
}
如果我們不指定xib名稱,loadView就會加載與控制器同名的xib(TestViewController.xib)
c.不指定xib名稱2
我們先將TestViewController.xib這個文件刪除掉。
當(dāng)沒有指定xib名稱,且沒有與控制器同名的xib時,會加載前綴與控制器名相同而不帶controller的xib(TestView.xib)。
3.不通過sb\xib加載
將TestView.xib這個文件也刪除掉,再來運行程序,結(jié)果是黑色的。
控制器view是存在的,只不過顏色為clearColor,所以看到的黑色其實是UIWindow的。
4.重寫loadView方法
我們重寫TestViewController的loadView方法,里面不做任何事
- (void)loadView { }
結(jié)果跟上面一樣黑,不同的是,這次并沒有創(chuàng)建view,最外層并不是UIView
如果我們希望控制器view加載出來的時候不是UIView而是其他控件,比如UIImageView,那我們就可以重寫loadView
- (void)loadView
{
self.view = [[UIImageView alloc] init];
}
結(jié)論
1.重寫loadView方法,則會根據(jù)重寫的loadView方法創(chuàng)建view
2.控制器通過storyboard加載,則根據(jù)storyboard的描述創(chuàng)建view
3.控制器view通過xib加載,則根據(jù)nibName對應(yīng)的xib創(chuàng)建view
4.沒有指定nibName,則根據(jù)與控制器同名的xib創(chuàng)建view
5.沒有同名的xib,則根據(jù)與控制器名前綴相同不帶controller的xib創(chuàng)建view
6.如果都沒有,則創(chuàng)建一個空白的xib
7.awakeFromNib當(dāng)控制器從nib加載的時候就會調(diào)用這個方法
8.storyboard加載的是控制器及控制器view,而xib加載的僅僅只是控制器的view