談?wù)凮bjective-C的Runtime機(jī)制

1. 什么是Runtime機(jī)制

? ? ?Runtime[1]是一套比較底層的C語言庫, 由一系列函數(shù)和數(shù)據(jù)結(jié)構(gòu)組成,包含了很多底層的C語言API。它主要是完成了Objective-C (OC)運(yùn)行時(shí)的兩件事:類的封裝和消息傳遞。

? ? ?本文從1. 類的封裝管理 2.消息傳遞?總結(jié)在我學(xué)習(xí)中對Runtime的認(rèn)識,隨后總結(jié)我所能理解到的Runtime機(jī)制的優(yōu)劣。

2. 類的封裝

? ? ?由于Runtime主要是用C語言來實(shí)現(xiàn),因此可以看到Runtime.h中,大量使用了struct來描述對象和類,而也使用了許多方法來封裝函數(shù)和結(jié)構(gòu)體,這使得OC在運(yùn)行中具有了面向?qū)ο蟮奶匦浴?/p>

? ? ?首先,OC的實(shí)例(Instance),類(Class)以及相對應(yīng)元類(metaClass)的繼承體系如圖


????類的實(shí)例通過isa指針指向這個(gè)實(shí)例的Class,這個(gè)對象的Class中的isa指針則指向這個(gè)類的metaClass。當(dāng)我們向一個(gè)實(shí)例發(fā)送消息,runtime會在這個(gè)實(shí)例isa指向的Class方法列表中查找方法;而向一個(gè)類發(fā)送消息時(shí),會在該類的metaClass的方法列表中進(jìn)行查找。

? ? ?在這個(gè)繼承體系的實(shí)現(xiàn)中,Runtime通過大量以class_,objc_為前綴的方法(例如下面的兩個(gè)方法)來封裝結(jié)構(gòu)體和函數(shù)。使得使用者可以直接操作Class,添加方法,協(xié)議等,也可以操作成員變量和屬性。

? ??在此基礎(chǔ)上,Runtime所實(shí)現(xiàn)最強(qiáng)大的功能是支持在程序運(yùn)行的時(shí)候創(chuàng)建和修改類,實(shí)例操作函數(shù)[2]和運(yùn)行時(shí)創(chuàng)建關(guān)聯(lián)對象(Associated Object)。 例如:當(dāng)我們想為Category增加成員變量時(shí),可以利用以下兩個(gè)函數(shù),在運(yùn)行時(shí)動態(tài)地增加成員變量:

? ??void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)

? ? id objc_getAssociatedObject(id object, const void *key)

3. 消息傳遞

? ? ?OC除了具有靜態(tài)語言的特征之外,也具有動態(tài)語言的特性:動態(tài)類型(Dynamic typing),動態(tài)綁定(Dynamic binding)和動態(tài)加載(Dynamic loading)。Runtime機(jī)制使得這三個(gè)特性得以實(shí)現(xiàn),讓程序可以在運(yùn)行時(shí)才判斷其該有的行為,而不是像靜態(tài)語言一樣在編譯時(shí)就確定了行為。

? ? ?一個(gè)OC程序在運(yùn)行時(shí),除了在上文第2部分提到的,還主要在以下兩個(gè)場景中運(yùn)用了Runtime機(jī)制:結(jié)合動態(tài)綁定的類實(shí)現(xiàn),結(jié)合動態(tài)類型的NSObject方法。三個(gè)方面互有關(guān)聯(lián),也有不同,流程如下:

3.1 結(jié)合動態(tài)綁定的類實(shí)現(xiàn)

首先,一個(gè)OC程序在調(diào)用一個(gè)方法時(shí)的流程如下:? ?

發(fā)送消息 -> 動態(tài)方法決議機(jī)制 -> 消息轉(zhuǎn)發(fā)

步驟一:?發(fā)送消息(Message):若成功則調(diào)用方法,若失敗且實(shí)現(xiàn)了動態(tài)方法決議機(jī)制則進(jìn)入步驟二

步驟二:動態(tài)方法決議機(jī)制(Dynamic Method Resolution): 若成功則調(diào)用方法,若失敗且實(shí)現(xiàn)了消息轉(zhuǎn)發(fā)機(jī)制則進(jìn)入Step 3

步驟三:消息轉(zhuǎn)發(fā)(Messaging Forwarding): 如提供了消息轉(zhuǎn)發(fā),則不會有錯(cuò)誤提示。不提供則報(bào)錯(cuò)

三個(gè)步驟執(zhí)行是有先后順序且不相關(guān)。下面通過順序分析三個(gè)步驟來解釋Runtime在其中的應(yīng)用:

3.1.1 三個(gè)步驟完成調(diào)用方法

Step 1: 消息傳遞(Message)

? ? ?OC程序運(yùn)行時(shí)依賴消息傳遞來實(shí)現(xiàn)動態(tài)綁定的方法調(diào)用:當(dāng)我們需要調(diào)用一個(gè)實(shí)例的方法,程序所做的是在運(yùn)行時(shí)才確定向某個(gè)類的receiver發(fā)送消息,receiver在收到消息后,從自身的實(shí)現(xiàn)中通過@selector/SEL去尋找響應(yīng)這條消息對應(yīng)的IMP。

? ??//默認(rèn)帶有兩個(gè)隱形的參數(shù) ,若有參數(shù)則為objc_msgSend(receiver, selector, arg1, ...)

?????[receiver messasge] -> objc_msgSend(receiver, selector)

? ? ?在上面的轉(zhuǎn)化中,objc_msgSend會通過receiver的isa指針找到receiver對應(yīng)的類,再在該類或該類的父類(沿著繼承體系繼續(xù)向上查找,如圖3.1)中的struct objc_cache *cache 和 struct objc_method_list ** methodLists尋找到所需方法。[3]

????在objc_msgSend函數(shù)中。首先通過obj的isa指針找到obj對應(yīng)的class。在Class中先去cache中 通過SEL查找對應(yīng)函數(shù)method(猜測cache中method列表是以SEL為key通過hash表來存儲的,這樣能提高函數(shù)查找速度)

????若 cache中未找到。再去methodList中查找,若methodlist中未找到,則取superClass中查找。若能找到,則將method加 入到cache中,以方便下次查找,并通過method中的函數(shù)指針跳轉(zhuǎn)到對應(yīng)的函數(shù)中去執(zhí)行。


Step 2: 動態(tài)方法決議機(jī)制(Dynamic Method Resolution)

? ? ?這個(gè)機(jī)制是在當(dāng)在子類到父類都尋找不到對應(yīng)的方法時(shí),讓我們能夠在運(yùn)行時(shí)動態(tài)地為一個(gè)selector提供實(shí)現(xiàn)。依賴NSObject.h中的如下方法為類或?qū)嵗黾有碌念惙椒ɑ驅(qū)嵗椒ā?/p>

? ? + (BOOL)resolveClassMethod:(SEL)sel __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);

? ? + (BOOL)resolveInstanceMethod:(SEL)sel __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);

Step 3: 消息轉(zhuǎn)發(fā)(Messaging Forwarding)

? ? ?Runtime會把消息有關(guān)的全部細(xì)節(jié)都封裝到NSInvocation對象中,再給receiver最后一次機(jī)會,令其設(shè)法解決當(dāng)前還未處理的這條消息。

? ? ?Runtime會通過回調(diào)一個(gè)類方法來尋求動態(tài)添加方法的支持。如果receiver仍然無法正常響應(yīng),則Runtime會繼續(xù)向receiver詢問是否有其它對象可以處理這條消息,若返回能夠處理的對象,Runtime會把消息轉(zhuǎn)給返回的對象,消息轉(zhuǎn)發(fā)流程也就結(jié)束。


3.1.2 利用Method Implementations(IMP)直接調(diào)用方法實(shí)現(xiàn)

? ? ?之前在3.1.1中提到了消息傳遞過程中通過@selector/SEL去查找消息對應(yīng)的IMP。而我們也可以通過IMP省去Runtime消息傳遞過程中的查找操作。

? ? ?先看看Runtime.h中可以看到關(guān)于一個(gè)方法的結(jié)構(gòu)定義如下,這個(gè)結(jié)構(gòu)實(shí)際上完成了SEL和對應(yīng)IMP的一個(gè)映射

? ?? struct objc_method {

? ? ? ? SEL method_name? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

? ? ? ? char *method_types? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

? ? ? ? IMP method_imp? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

? ? }

????method_types:char指針,存儲著方法的參數(shù)類型和返回值類型。

? ? ?SEL:一個(gè)Int類型的一個(gè)地址,地址中存放著方法的名字,它僅僅和方法名相關(guān)。因此不同類中可能存在擁有相同SEL的方法。工程中所有的SEL形成了一個(gè)Set。我的理解這個(gè)Set的存在是為了提高方法的查詢速度。

? ? ?IMP:它本質(zhì)可以說是一個(gè)函數(shù)指針,即方法代碼的入口點(diǎn),定義如下:

? ?? id (*IMP)(id, SEL, ...)

????因此IMP也可以在當(dāng)一個(gè)消息要被發(fā)送給某個(gè)對象很多次的時(shí)候直接調(diào)用(例如下例在for循環(huán)中調(diào)用多次setFill:,直接使用NSObject類中的methodForSelector:可以獲得一個(gè)指向方法實(shí)現(xiàn)的指針來進(jìn)行優(yōu)化)。 這樣省去了Runtime消息傳遞時(shí)的查找操作,會比直接向?qū)ο蟀l(fā)送消息高效一些。

3.2 結(jié)合動態(tài)類型的NSObject方法

? ? ?由于大部分的對象都是由NSObject繼承而來。因此也繼承了了NSObject的屬性和內(nèi)存分配方法(如下),這些方法均用于在運(yùn)行時(shí)取得信息的方法。

?- (BOOL)isKindOfClass:(Class)aClass;

? - (BOOL)isMemberOfClass:(Class)aClass;

?- (BOOL)conformsToProtocol:(Protocol *)aProtocol;

動態(tài)特性的靈活使得id類型在Protocol-Delegate的實(shí)現(xiàn)中也大量被使用,主要是把delegate指針類型定義為id,從而使得運(yùn)行時(shí)實(shí)現(xiàn)動態(tài)替換。

4. Runtime的應(yīng)用

借用 http://www.itdecent.cn/p/ab966e8a82e2 的圖

4.1 動態(tài)添加一個(gè)類(KVO的能夠?qū)崿F(xiàn)也依賴于這個(gè)特性)

4.2 字典轉(zhuǎn)模型 & 自動歸檔解檔?

4.3 動態(tài)交換類方法/對象方法/系統(tǒng)方法

5. Runtime機(jī)制的優(yōu)劣

優(yōu)點(diǎn):更加靈活。

? ? ?Runtime環(huán)境注冊所有全局的類,函數(shù),變量等等信息等等,我們可以無限的為這個(gè)層增加必要的功能,寫代碼時(shí)更具靈活性。調(diào)用函數(shù)時(shí)候,會先從這個(gè)運(yùn)行時(shí)環(huán)境里檢測所以所有的可能,而并不是JMP到一個(gè)非法地址就一定會Crash。這就極大增加了程序的靈活性。

缺點(diǎn):增加損耗。

? ? ?很顯然Runtime的機(jī)制帶來了CPU計(jì)算的損耗。而也可以看出Runtime機(jī)制為了提高效率也設(shè)計(jì)了SEL, objc_cache *cache來緩存使用過的selector及對應(yīng)的方法的地址等方法,從而提高效率。

5. Reference

[1] AppleRuntime Guide

[2] Objective-C Runtime 運(yùn)行時(shí)之一:類與對象

[3] Objective-C總Runtime的那點(diǎn)事兒

[4] 深入學(xué)習(xí)Objective-C語言的動態(tài)特性:

[5] Understanding the Objective-C Runtime

[6] 刨根問題Objective-C Runtime

[7] Objective-C 與 Runtime:為什么是這樣?

http://www.itdecent.cn/p/ab966e8a82e2

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,054評論 0 9
  • 我們常常會聽說 Objective-C 是一門動態(tài)語言,那么這個(gè)「動態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,335評論 0 7
  • 轉(zhuǎn)載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 832評論 0 2
  • 本文詳細(xì)整理了 Cocoa 的 Runtime 系統(tǒng)的知識,它使得 Objective-C 如虎添翼,具備了靈活的...
    lylaut閱讀 866評論 0 4
  • 文中的實(shí)驗(yàn)代碼我放在了這個(gè)項(xiàng)目中。 以下內(nèi)容是我通過整理[這篇博客] (http://yulingtianxia....
    茗涙閱讀 1,028評論 0 6

友情鏈接更多精彩內(nèi)容