單純?yōu)榱俗鲰?xiàng)目你要不太了解底層的知識(shí)可能對(duì)你項(xiàng)目的實(shí)現(xiàn)影響不是很大(前提是不追求極致的項(xiàng)目????)。但是如果不知道點(diǎn)UIViewController的知識(shí)你想做項(xiàng)目那就有點(diǎn)難了。 因?yàn)樽龅捻?xiàng)目離不開各種各樣的UIViewController,所以我就帶你聊聊UIViewController的生命周期也就是它的 “初始化” 到 "釋放" 的過程。
前言:
iOS中最離不開的就是UIViewController,為啥它這么重要就不介紹了。
生命周期簡單概括有如下幾步
初始化 --> 加載視圖 -->配置視圖 -->顯示 -->消失 -->銷毀
初始化方式
- xib文件
- 純代碼
- storyboard加載
- (instancetype)init (這個(gè)在UIViewController的API中是沒列出的)
- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil ;
- (instancetype)initWithCoder:(NSCoder *)aDecoder ;
以上代碼是官方API羅列出來的初始化方法下面一一介紹
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil ;
- nibNameOrNil --> nib的名字
- nibBundleOrNil --> 查找nib文件的bundle名字,一般指定為nil 進(jìn)行自動(dòng)搜索
該方法的使用也有幾個(gè)情況

-
情況一
BBBViewController *VC = [[BBBViewController alloc] initWithNibName:@"BBBViewController" bundle:nil];
該情況也是大家的通常做法,就是nib的名字跟控制器的類名 “一致“ 看一下效果。

-
情況二
BBBViewController *VC = [[BBBViewController alloc] initWithNibName:@"OtherViewController" bundle:nil];
該情況是nib的名字跟控制器的類名 ”不一致“ 看一下效果。
代碼運(yùn)行效果 情況三
BBBViewController *VC = [[BBBViewController alloc] initWithNibName:nil bundle:nil];
參數(shù)指定為nil的時(shí)候它的加載情況有3種:
- 如果有跟控制器類名一致的(也就是BBBViewController.xib)就加載該文件,這個(gè)效果跟上面介紹的(情況一)是一樣的。
- 如果沒有跟控制器類名一致的xib文件的時(shí)候,也會(huì)自動(dòng)匹配項(xiàng)目中的xib文件。但這個(gè)xib文件的名字一定是控制器類名去除掉Controller之后剩下的名字(例:BBBViewController控制器,那么會(huì)自動(dòng)匹配BBBView.xib文件),其中這個(gè)場(chǎng)景有一個(gè)關(guān)鍵點(diǎn)如果想以這樣的方式匹配xib文件那么你的控制器的名字一定是以ViewController結(jié)尾的(***ViewController)否則不能匹配到這個(gè)情況。
- 如果項(xiàng)目中沒有上述的2個(gè)情況,那么控制器就會(huì)無xib加載也就是創(chuàng)建一個(gè)空的view
- (instancetype)init;
這個(gè)方法跟調(diào)用BBBViewController *VC = [[BBBViewController alloc] initWithNibName:nil bundle:nil];效果一樣
唯一一個(gè)區(qū)別就是這個(gè)方法會(huì)首先調(diào)用一下 initWithNibName:bundle:方法然后再調(diào)用一次alloc] init]方法(initWithNibName:bundle:初始化控制器時(shí)候不走該方法)
- (instancetype)initWithCoder:(NSCoder *)aDecoder ;
如果ViewController是從storyboard初始化的話,那么會(huì)執(zhí)行上面這個(gè)方法,但是跟加載xib文件有一個(gè)不同就是從storyboard加載VC的時(shí)候還會(huì)走 - (void)awakeFromNib 這個(gè)方法
總結(jié)
- 通常我們需要做的是在ViewController的初始化階段對(duì)用到的數(shù)據(jù)進(jìn)行初始化的操作,切記不要在這個(gè)位置對(duì)view進(jìn)行操作(例如self.view),因?yàn)檫@個(gè)時(shí)候VC還沒有初始化完成哪來的view, 如果這個(gè)時(shí)候手動(dòng)調(diào)用的話會(huì)導(dǎo)致整個(gè)VC的流程出錯(cuò)。
- 通過上面的介紹知道VC的初始化代碼的幾種方式,尤其是加載xib的時(shí)候的情況。不過為了可閱讀性還是建議xib的文件名跟VC的類名保持一致。
- (void)loadView
初始化完成就要真正的加載VC的view了,VC的view加載是一種懶加載的方式,用到的時(shí)候會(huì)自動(dòng)調(diào)用。所以不要手動(dòng)調(diào)用該方法,這個(gè)方法在VC的整個(gè)過程中可能會(huì)被調(diào)用多次。
有2種情況我們是一定不要重寫這個(gè)方法的
- view是從xib文件中加載的
- view是從storyboard中加載
以上兩種情況如果你重寫了loadView方法那么你的文件上的布局統(tǒng)統(tǒng)作廢
總結(jié): 一般我們是不需要重寫這個(gè)方法的,除非你對(duì)VC的view有特殊的要求,系統(tǒng)默認(rèn)是給我創(chuàng)建一個(gè)空的view作為VC的視圖。如果你自己想創(chuàng)建了一個(gè)view作為VC的view,那么一定要把自定義的view賦值給VC.view否則將認(rèn)為VC的view是空值,這樣會(huì)使VC陷入死循環(huán)。
- (void)viewDidLoad
當(dāng)VC的view被創(chuàng)建完成之后也就是執(zhí)行完loadView以后執(zhí)行該方法。通常我們需要在這個(gè)方法里面來配置UI。因?yàn)檫@個(gè)時(shí)候view是確確實(shí)實(shí)的存在了,可以對(duì)其進(jìn)行一些子視圖的添加等。
注意: 該方法通常是執(zhí)行一次,但其實(shí)他跟loadView這個(gè)方法一樣也存在執(zhí)行多次的情況,通過上面的介紹我們知道如果這個(gè)界面的view被重新加載那么一定順其自然的執(zhí)行該方法。
舉例說明:A控制器 push B控制器 如果這時(shí)收到了內(nèi)存警告隨即會(huì)調(diào)用A控制器中的didReceiveMemoryWarning方法,如果你在A和B控制器的didReceiveMemoryWarning方法對(duì)沒用的變量釋放以及銷毀其view的操作(self.view = nil) 那么當(dāng)你從B返回A的時(shí)候因?yàn)锳的view被釋放了,所以還會(huì)重新走viewDidLoad的方法。這個(gè)場(chǎng)景主要取決于你對(duì)內(nèi)存警告時(shí)候的處理。
- (void)viewWillAppear && - (void)viewDidAppear
這兩個(gè)方法是成對(duì)的,所以一起介紹viewWillAppear正常是在viewDidLoad之后調(diào)用的,但是該方法不一定是在viewDidLoad之后立即被調(diào)用。調(diào)用該方法的時(shí)刻是view顯示的時(shí)候,而且隨著控制器Push / Present 以及對(duì)應(yīng)的Pop / Dismiss 等方法會(huì)使該方法執(zhí)行多次。
- 舉例:
// 這個(gè)方法是不會(huì)調(diào)用viewWillAppear的,因?yàn)槟阒皇浅跏蓟薞C并沒有讓view顯示出來
BBBViewController *VC = [[BBBViewController alloc] init];
// 這個(gè)方法是不會(huì)調(diào)用viewWillAppear
// 雖然你調(diào)用了vc.view但沒有加到可以顯示的圖層同樣不用調(diào)用
BBBViewController *VC = [[BBBViewController alloc] init];
VC.view.frame = CGRectMake(100, 100, 100, 100);
// 這個(gè)方法是會(huì)調(diào)用viewWillAppear,因?yàn)関iew加載到了父視圖上
BBBViewController *VC = [[BBBViewController alloc] init];
VC.view.frame = CGRectMake(100, 100, 100, 100);
[AAAVC.view addSubview:VC.view];
注意 viewWillAppear執(zhí)行完了,未必會(huì)執(zhí)行viewDidAppear,文章后面會(huì)舉例說明。
- (void)viewWillLayoutSubviews && - (void)viewDidLayoutSubviews
來看一下官方給的解釋
// Called just before the view controller's view's layoutSubviews method is invoked. Subclasses can implement as necessary.
- (void)viewWillLayoutSubviews ;
// Called just after the view controller's view's layoutSubviews method is invoked. Subclasses can implement as necessary.
- (void)viewDidLayoutSubviews ;
這2個(gè)方法也是成對(duì)的,這段英文的注釋大家肯定是一目了然, 一個(gè)是在 VC.view 的layoutSubviews被調(diào)用之前,另一個(gè)是在layoutSubviews被調(diào)用之后。這里不介紹layoutSubviews方法的調(diào)用機(jī)制。
注意 如果你的布局使用autolayout的時(shí)候,我們可能需要在控制器中使用代碼來獲取一些視圖的frame,這個(gè)時(shí)候就需要注意一定要在viewDidLayoutSubviews中獲取,只有在viewDidLayoutSubviews中視圖的frame才是準(zhǔn)確的。
- (void)viewWillDisappear && - (void)viewDidDisappear
同樣成對(duì)的2個(gè)方法,當(dāng)界面消失的時(shí)候例如pop/dismiss 操作的時(shí)候會(huì)先后執(zhí)行這兩個(gè)方法。
BBBViewController *VC = [[BBBViewController alloc] init];
[self addChildViewController:VC];
VC.view.frame = CGRectMake(100, 100, 100, 100);
[self.view addSubview:VC.view];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[VC.view removeFromSuperview];
});
上面這一段代碼雖然不是pop/dismiss 依然會(huì)執(zhí)行這兩個(gè)方法,所以這兩個(gè)方法被調(diào)用的時(shí)機(jī)就是VC.view消失的時(shí)候 (消失不等于隱藏)
注意 viewWillDisappear執(zhí)行完了,未必會(huì)執(zhí)行viewDidDisappear,文章后面會(huì)舉例說明。
- (void)didReceiveMemoryWarning
當(dāng)系統(tǒng)的內(nèi)存不足時(shí),會(huì)調(diào)用didReceiveMemoryWarining方法,我們需要做的就是釋放掉部分暫時(shí)不用的資源。例:
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
if (!self.view.window && self.isViewLoaded) {
self.view = nil; //釋放掉view等操作
}
}
- (void)dealloc
該方法只有在 ViewController 徹底被釋放的時(shí)候調(diào)用。例如pop/dismiss,如果VC中的代碼不夠規(guī)范引起了循環(huán)引用等問題,即使執(zhí)行了pop/dismiss并且界面也消失了但并沒有真的釋放。要注意這個(gè)方面。
控制器之間跳轉(zhuǎn)場(chǎng)景舉例
舉例說明:現(xiàn)在有AAA控制器 和 BBB控制器。模擬在AAA控制器push到BBB控制器中的場(chǎng)景。下面這段代碼寫在AAA控制器中:
BBBViewController *VC = [[BBBViewController alloc] initWithNibName:nil bundle:nil];
//BBBViewController *VC = [[BBBViewController alloc] init]; //使用這句代碼初始化也可以
[self.navigationController pushViewController:VC animated:YES];
當(dāng)我們執(zhí)行AAA push到 BBB的時(shí)候
- initWithNibName:bundle:(BBBViewController執(zhí)行的)
- viewDidLoad: (BBBViewController執(zhí)行的)
- viewWillDisappear:(AAAViewController執(zhí)行的,將要消失)
- viewWillAppear:(BBBViewController執(zhí)行的)
- viewWillLayoutSubviews:(BBBViewController執(zhí)行的)
- viewDidLayoutSubviews:(BBBViewController執(zhí)行的)
- viewDidDisappear:(AAAViewController執(zhí)行的,已經(jīng)消失)
- viewDidAppear:(BBBViewController執(zhí)行的)
注意:上面提到的消失不是指的銷毀
當(dāng)我們執(zhí)行BBB pop返回到 AAA的時(shí)候
- viewWillDisappear:(BBBViewController執(zhí)行的)
- viewWillAppear:(AAAViewController執(zhí)行的)
- viewDidDisappear:(BBBViewController執(zhí)行的)
- viewDidAppear:(AAAViewController執(zhí)行的)
- dealloc:(BBBViewController執(zhí)行的)
當(dāng)我們?cè)贐BB界面 用手勢(shì)向右拖動(dòng)返回 AAA的時(shí)候
當(dāng)我們?cè)贐BB界面手勢(shì)拖動(dòng)讓BBB界面和AAA界面都出現(xiàn)在屏幕上,但最后松開還是停留在BBB界面上這個(gè)時(shí)候的執(zhí)行代碼有一點(diǎn)不同,來看一下:
- viewWillDisappear:(BBBViewController執(zhí)行的,將要消失的)
- viewWillAppear:(AAAViewController執(zhí)行的,要返回到A也就是將要出現(xiàn)了)
- viewWillDisappear:(AAAViewController執(zhí)行的,因?yàn)槲覀冏詈笸T贐上所以剛才的A還要執(zhí)行一次這個(gè)方法)
- viewDidDisappear:(AAAViewController執(zhí)行的)
- viewWillAppear:(BBBViewController執(zhí)行的,剛剛執(zhí)行了將要消失所以現(xiàn)在重新出現(xiàn)要執(zhí)行這個(gè)方法)
- viewDidAppear:(BBBViewController執(zhí)行的,最終停留在B上)
