1. 程式人生 > >IOS —— 控制器載入及UI控制元件初始化過程 及相關的那些事

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學習的內容也是夠多夠嗆的,消化過程中琢磨變成了部落格中的該篇文章,在堅持中慢慢儲備更多的知識吧!

我也知道文章寫的不咋滴,就當做是俺做做筆記吧~