iOS重要知識-- KVC、KVO、runloop、runtime

Auditore發表於2017-12-29

作為一個基礎不牢,嘴上沒毛的開發者,對於KVC、KVO、runtime、runloop這些面試筆試考爛了,平時卻不怎麼用得上的東西,有時候遇上時是一臉問號❓。既然面試筆試一定會考,而且還一知半解,那就一次搞定它們,省的以後被面試虐。從網上搜羅了大量的資料,不停地篩選,加上自己淺薄的理解後,於是有了這篇儘量讓自己看得明白的筆記。


KVC

相信即使沒用過,你也大概聽到過。KeyValueCoding。用自己話簡單概括下,就是通過例項變數或者成員變數的屬性名稱,來訪問其屬性或者說使用它們的getter、setter方法。具體如下:

假如我們有個叫做Student的類,它有個成員變數叫做age,還有個叫做name。兩個屬性叫做age1、name1。我們知道,用@property修飾過的屬性們,編譯器給我們生成了getter、setter方法。呼叫的時候,我們只需要用點語法就能呼叫。但成員變數,如果沒有重寫他們setter、getter方法,我們怎麼辦。

student*stu = [[student alloc]init];

NSString*name = [stu valueForKey:@"name"];

 [stu setValue:@"axiba"forKey:@"name"];

如上,首先是例項化一個變數stu,然後使用valueForKey獲取該物件的name屬性,賦值給name變數。然後setValue Forkey方法對stu的name屬性進行賦值。注意,這裡最關鍵的引數是key值,千萬別寫錯。

好了KVC講完,我們繼續講KVO。

KVO

KeyValueObserving。換句話說,這叫鍵值對監測,利用例項變數觀察某例項屬性值的變化(這裡的觀察者和被觀察者不一定是同一個,也就是說,可以用一個物件觀測另一個物件的某個屬性變化,來達到對觀察者進行操作的目的)。KVO 的實現也依賴於 Objective-C 強大的 Runtime 。KVO 是 Objective-C 對觀察者模式(Observer Pattern)的實現。也是 Cocoa Binding 的基礎。當被觀察物件的某個屬性發生更改時,觀察者物件會獲得通知。使用步驟:

1.註冊需要觀察的物件的屬性addObserver:forKeyPath:options:context:

//第一個引數observer:觀察者物件

//第二個引數keyPath: 被觀察的屬性名稱

//第三個引數options: 觀察屬性的新值、舊值等的一些配置(列舉值,可以根據需要設定,例如這裡可以使用兩項)

//第四個引數context: 上下文,可以為kvo的回撥方法傳值(例如設定為一個放置資料的字典)

[被觀察者 addObserver:觀察者 forKeyPath:@"被觀察者的屬性名稱"options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNewcontext:nil];

2.實現observeValueForKeyPath:ofObject:change:context:方法,這個方法當觀察的屬性變化時會自動呼叫

//keyPath:屬性名稱

//object:被觀察的物件

//change:變化前後的值都儲存在change字典中

//context:註冊觀察者時,context傳過來的值-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)objectchange:(NSDictionary *)changecontext:(void*)context{

}

3.取消註冊觀察removeObserver:forKeyPath:context:

看起來挺像NSNotification的,需要注意的是千萬別把keyPath寫錯,還有就是別呼叫兩次的removeObserve會導致程式崩潰,你可能說,這麼低階的錯誤我怎麼可能會犯。當然寫兩句removeObserve的確不大可能,除非手滑。但如果你的父類和子類都寫了removeObserve,就不易看不來了,這時候也會導致崩潰。

還有需要注意的是,addobserver方法,是被觀察者新增了一個觀察者,來檢測自己某個屬性的變化,這個被觀察者可以使別的類的物件,也可以是本類的物件。


runloop

RunLoop顧名思義,就是執行迴圈的意思。Runloop是事件接收和分發機制的一個實現。Runloop提供了一種非同步執行程式碼的機制,不能並行執行任務。在主佇列中,Main RunLoop直接配合任務的執行,負責處理UI事件、定時器以及其他核心相關事件。

(1).RunLoop的主要目的:保持程式的持續執行、處理App中的各類事件(觸控事件、定時器事件、Selector事件)、節省CPU資源,提高程式效能,沒有事件時就進入睡眠狀態。

(2).什麼時候使用Runloop :1.需要使用Port(基於埠)或者自定義(事件源)Input Source與其他執行緒進行通訊。2.需要線上程中使用Timer。3.需要線上程上使用performSelector*****方法。4.需要讓執行緒執行週期性的工作。

主執行緒預設有Runloop。當自己啟動一個執行緒,如果只是用於處理單一的事件,則該執行緒在執行完之後就退出了。所以當我們需要讓該執行緒監聽某項事務時,就得讓執行緒一直不退出,runloop就是這麼一個迴圈,沒有事件的時候,一直休眠,有事件來臨了,執行其對應的函式。

這些就是runloop的基本重點,至於怎麼實現,就不詳述了,網上很多。runloop是runtime訊息轉發機制的具體實現。

runtime

RunTime簡稱執行時。就是系統在執行的時候的一些機制,其中最主要的是訊息機制。對於C語言,函式的呼叫在編譯的時候會決定呼叫哪個函式。

runtime我們看一行程式碼,一張圖。

[self goHome];//這是我們常見的方法呼叫,通過clang編譯器,它卻是這樣的

objc_msgSend(obj,@selector(goHome));

首先,編譯器將程式碼[obj goHome];轉化為objc_msgSend(obj,@selector(GoHome));,在objc_msgSend函式中。首先通過obj的isa指標找到obj對應的class。在Class中先去cache中通過SEL查詢對應函式method(猜測cache中method列表是以SEL為key通過hash表來儲存的,這樣能提高函式查詢速度),若cache中未找到。再去methodList中查詢,若methodlist中未找到,則取superClass中查詢。若能找到,則將method加入到cache中,以方便下次查詢,並通過method中的函式指標跳轉到對應的函式中去執行。

下面關於SEL方法選擇器,著重去學習了下,查閱到如下資料。

SEL其主要作用是快速的通過方法名字(goHome)查詢到對應方法的函式指標,然後呼叫其函式。SEL其本身是一個Int型別的一個地址,地址中存放著方法的名字。對於一個類中。每一個方法對應著一個SEL。所以iOS類中不能存在2個名稱相同的方法,即使引數型別不同,因為SEL是根據方法名字生成的,相同的方法名稱只能對應一個SEL。Objective-C在編譯時,會根據方法的名字生成一個用來區分這個方法的唯一的一個ID。只要方法名稱相同,那麼它們的ID就是相同的。兩個類之間,不管它們是父類與子類的關係,還是之間沒有這種關係,只要方法名相同,那麼它的SEL就是一樣的。每一個方法都對應著一個SEL。編譯器會根據每個方法的方法名為那個方法生成唯一的SEL。這些SEL組成了一個Set集合,當我們在這個集合中查詢某個方法時,只需要去找這個方法對應的SEL即可。而SEL本質是一個字串,所以直接比較它們的地址即可。

當然,不同的類可以擁有相同的selector。不同類的例項物件執行相同的selector時,會在各自的方法列表中去根據selector去尋找自己對應的IMP。

對了,還剩下一張圖。

iOS重要知識-- KVC、KVO、runloop、runtime
runtime訊息轉發機制

metaclass就是元類。指向metaclass,也就是靜態的Class。一般一個Obj物件中的isa會指向普通的Class,這個Class中儲存普通成員變數和物件方法(“-”開頭的方法),普通Class中的isa指標指向靜態Class,靜態Class中儲存static型別成員變數和類方法(“+”開頭的方法)。Class所有metaclass中isa指標都指向跟metaclass。而跟metaclass則指向自身。Root metaclass是通過繼承Root class產生的。與root class結構體成員一致,也就是前面提到的結構。不同的是Root metaclass的isa指標指向自身。

super_class:指向父類,如果這個類是根類,則為NULL。


總算搞完了,iOS開發者如果只要能幹活,那麼可能你壓根不需要知道上面這些令人煩躁又枯燥的東西,但這些卻是衡量一個iOS開發者進入中級階段必須要知道的內容,不然面試官估計直接把你pass了……即使以後你被錄用了,工作上用不到這些,但你還是得懂。所以一遍不懂就兩遍,兩遍不懂就三遍。雖然是笨辦法,但又有什麼關係,能成功就好了。蝸牛精神,一步一步往上爬!

相關文章