IOS —— 控制器載入及UI控制元件初始化過程 及相關的那些事
還是喜歡說白話文的我!這回閒話少說進入正題
1.ViewController(控制器) 載入過程
我們知道,當我們需要跳轉一個頁面的時候,會新建一個Viewcontroller。建立一個鏈橋通過navigationController跳轉過去。
那麼這一個過程裡究竟執行了什麼方法發生了什麼呢?
我們新建一個叫XgViewController的檔案,並且建立一個xib檔案。
這裡我們重寫一下init以及initWithNib的方法,並且對應在方法實際執行前列印一下當前執行的是什麼方法
(__func__ 該引數可以直接打印出當前方法)
- (instancetype)init { NSLog(@"%s",__func__); self = [super init]; if (self) { } return self; } - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { NSLog(@"%s",__func__); self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { }return self; }
以下是執行的日誌檔案
2018-12-12 23:34:17.438869+0800 Second_Class[28117:2101088] -[XgViewController init]
2018-12-12 23:34:17.439035+0800 Second_Class[28117:2101088] -[XgViewController initWithNibName:bundle:]
當我們使用init方法建立控制器時,會自動的執行initWithNibName方法。這裡說明了
init方法裡頭封裝了initWithNibName方法。執行init方法時會自動執行後者。反之則不執行。
但此時問題出現了。重寫方法後跳轉頁面是純黑色的。意味著XgViewController的頁面並沒有初始化成功。
也說明了xib沒有被找到。這是為什麼?原因也很簡單
原因在initWithNibName方法中
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
當方法中nibNameOrNil = nil 的時候。方法預設會尋找與類名相同的xib檔案。
當方法中nibBundleOrNil = nil 的時候。方法會預設尋找MainBudle。
當我們重寫了方法,這些預設值也就煙消雲散了。
這說明當自己需要控制一個xib頁面時,應該將需要控制的程式碼細則寫入initWithNibName中。
所以為了能正確的利用xib,建立方法應該這樣寫
XgViewController *xgVC = [[XgViewController alloc] initWithNibName:@"XgViewController" bundle:nil];
這樣跳轉就沒有黑屏了。問題1解決
那麼繼續回到我們載入過程中來。
我們繼續在XgViewController中
對控制器生命週期中的個別方法以及預設封裝的方法分別新增列印 __func__ 引數檢視下他們執行的順序
- (void)loadView
- (void)viewDidLoad
- (void)viewWillAppear:(BOOL)animated
- (void)viewWillLayoutSubviews
- (void)viewDidAppear:(BOOL)animated
這時候有人問為什麼是隻弄這些方法不整生命週期中的其他方法呀?
因為在控制器的建立中,這些方法扮演者關鍵的作用
建立控制器 - > 讀取視窗後 - > 視窗即將出現前 - > 窗口布局設定- > 窗口出現後
是這麼一個流程
2018-12-12 23:46:29.377394+0800 Second_Class[28197:2114504] -[XgViewController loadView]
2018-12-12 23:46:29.381996+0800 Second_Class[28197:2114504] -[XgViewController viewDidLoad]
2018-12-12 23:46:29.382476+0800 Second_Class[28197:2114504] -[XgViewController viewWillAppear:]
2018-12-12 23:46:29.394225+0800 Second_Class[28197:2114504] -[XgViewController viewWillLayoutSubviews]
2018-12-12 23:46:29.896049+0800 Second_Class[28197:2114504] -[XgViewController viewDidAppear:]
其中在loadView中,我們通常用到的控制器中的self.view便是在此建立,當我們當前控制器需要用到指定的自定義的視窗時。重寫該方法即可
這裡我建立一個XgView
- (void)loadView { [super loadView]; self.view = [[XgView alloc] init]; }
就這麼簡單的一步,就完成了View與controller的分離。我們可以在xgView中自定義需要的元素,並且可以利用Controller通過監聽xgView中的屬性變化來做出對應的操作。
那loadView方法為空時,呼叫self.view會怎麼樣?
這裡我們可以試著註釋loadView中的資料啟動下程式,結果會是怎麼樣呢
是死迴圈。
因為self.view本質為懶載入,當self.view為空時呼叫loadView方法。當loadView為空時將空的結果返回給self.view。
在過程中不斷的列印著viewDidLoad方法。直至Xcode提示死迴圈報錯。
那麼接下來還是退出當前控制器。隨之而來產生的一個問題是
當前頁面的消失是在新頁面出現前還是出現後呢?
真理在於實踐。這裡我們在當前頁面的ViewDidDisappear: 、新頁面中的ViewWillAppear: 分別列印一下__func__。這樣是不是就一目瞭然了
2018-12-13 00:08:12.863543+0800 Second_Class[28408:2147061] -[ViewController viewWillAppear:]
2018-12-13 00:08:13.368920+0800 Second_Class[28408:2147061] -[XgViewController viewDidDisappear:]
這是列印的結果。這時候很多人的就會想不明白,難道不是舊頁面消失,新頁面才會出現的嗎。為什麼會本末倒置呢?
這也是一道面試常問的題,為什麼呢?
因為Controller(控制器)的切換本質上也就是View(視窗)的切換。View的切換過程中如果按照 舊頁面消失 - > 新頁面出現這樣的方式來執行的話
在切換時,當新頁面未加載出來時會出現一段無View的狀態,整體呈黑色。造成的使用者體驗是極度不友好的。
所以當新View出現時,舊View才會消失。確保的是使用者體驗。
2.UI控制元件初始化過程
UI控制元件初始化過程與UIViewController一致,這裡為了區分開以UIButton舉例
UIButton在初始化過程中,是會自動執行init/initWithFrame 方法 。
執行init方法時自動執行initWithFrame,反之則不執行
同理,通過Xib建立UI控制元件時
nib在初始化過程中,是會自動執行initWithCoder/awakeFromXib 方法
既然和控制器初始化過程基本一致為什麼要特地抽出一個模組來講UI控制元件的初始化呢
原因在控制元件初始化的過程中有這麼一個方法
這裡依然是使用程式碼舉例
- (void)viewDidLoad { [super viewDidLoad]; _xgBtn = [[XgButton alloc] init]; [self.view addSubview:_xgBtn]; [self createButton]; } - (void)createButton { NSLog(@"1"); [_xgBtn setNeedsLayout]; NSLog(@"3"); }
在控制器中我例項化了一個xgBtn物件,並呼叫了xgBtn中的setNeedsLayout的方法。(方法中添加了輸出列印"2"的語句)
執行setNeedsLayout方法等同於執行xgBtn中的layoutSubViews(初始化佈局程式碼)
按照一般的邏輯來講,列印順序應該是1、2、3 按順序依次列印才對
但是輸出結果呢?
是1、3、2。
這時候有人會疑惑,我們不是按順序執行嗎。先列印1,然後利用setNeedsLayout佈局頁面,然後列印2,接下來列印3。
這樣理解是沒有錯的,不過那是建立在xgBtn中的layoutSubViews方法在當前執行緒執行。
蘋果官方因為擔心頁面資料載入與初始化影響app效能,在上期提到的runloop裡,將初始化應用與佈局程式碼分開成倆個迴圈進行處理。
上述程式碼中所寫到的[_xgBtn setNeedLayout];起到的作用僅僅是喚醒runloop,將該方法標記並且加入下一個處理的迴圈。
如果只是小專案並不在意效能方面的事情時,硬是要初始化與佈局程式碼放在同一條執行緒中執行時。
可以對當前物件採用以下方法
[_xgBtn layoutIfNeeded]; or [_xgBtn layoutSubviews]
將初始化插入當前的迴圈中。
3.UIView、CALayer的關係
CALayer和UIView中所實現的方法基本是一致,但不同的是
UIView可以響應事件,CALayer無法響應事件
UIView實際上就是CALayer的代理,是對CALayer方法的封裝,充當CALayer的代理物件
UIView之所以能顯示東西也是因為有CALayer的原因。
說響應事件可能還是有點含糊,所以倆者的區別是
UIView主要是對顯示內容的管理
CALayer主要是對顯示內容進行繪製
具體關於這個模組往後有時間抽出來單獨舉例子講,這裡不過多贅述
簡單結語:今天在IOS學習的內容也是夠多夠嗆的,消化過程中琢磨變成了部落格中的該篇文章,在堅持中慢慢儲備更多的知識吧!
我也知道文章寫的不咋滴,就當做是俺做做筆記吧~