為了充分介紹Objecive-C語言中RunTime機制复罐,使讀者對RunTime機制有一個清晰的了解。本文將從以下幾個部分來介紹相關(guān)知識:
- 第一部分將介紹Objecive-C語言的基本特性善绎。
- 第二部分將介紹Objecive-C類對象與isa指針的本質(zhì)。
- 第三部分將介紹RunTime整個流程。
- 第四部分將介紹RunTime的常見應(yīng)用場合。
第一部 Objecive-C語言基本特性
Objecive-C(以下簡稱OC)是一種采用消息結(jié)構(gòu)阔逼、面向運行時、動態(tài)的面向?qū)ο蟮恼Z言地沮。其運行時所執(zhí)行的代碼由運行環(huán)境來決定嗜浮。它本質(zhì)上是C的超集。
OC中重要的工作由“運行期組建”(runtime component)而非編譯器完成摩疑。
備注:一般來說面向?qū)ο蟮恼Z言有兩種實現(xiàn)形式:一種是消息結(jié)構(gòu)危融,另一種是采用函數(shù)調(diào)用。它們兩者的區(qū)別在于雷袋。后者運行時所執(zhí)行的代碼由編譯器決定吉殃,也就是說采用函數(shù)調(diào)用的語言在編譯期編譯器就會查找出運行是代碼所執(zhí)行的方法。采用消息結(jié)構(gòu)(Objecive-C)語言只有在運行時確定所執(zhí)行的代碼方法楷怒。
第二部 Objecive-C中對象與isa指針蛋勺。
“對象”是面向?qū)ο笳Z言的基本構(gòu)造單元,開發(fā)者可以通過對象來存儲和傳遞數(shù)據(jù)鸠删。理解“對象”對于理解消息傳遞機制有著重要的意義抱完。
要認識什么是isa指針,我們得先明確一點:
在OC中刃泡,任何類的定義都是對象巧娱。類和類的實例(對象)沒有任何本質(zhì)上的區(qū)別碉怔。任何對象都有isa指針。
描述OC對象所用的數(shù)據(jù)類型定義在運行期程序文件的頭文件里:
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;};
typedef struct objc_object *id;```
由此可見每個對象的首個成員時Class類的變量禁添,該對象稱為isa指針眨层。它指向?qū)ο蟮念悺?
類的定義也在頭文件里:
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}```
看出來NSObject定義了一個成員變量,我們繼續(xù)尋找:
typedef struct objc_class *Class;
/// Represents an instance of a class.
Class 是一個 objc_class 結(jié)構(gòu)類型的指針, id是一個 objc_object 結(jié)構(gòu)類型的指針上荡。
繼續(xù)打開objc_class,我們找到class的定義:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class &OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;```
從上可以看出:isa本質(zhì)上是一個Class 類型的指針趴樱。每個實例對象有個isa的指針,他指向?qū)ο蟮念悺6鳦lass里也有個isa的指針, 指向meteClass(元類)酪捡。元類保存了類方法的列表叁征。當類方法被調(diào)用時,先會從元類查找類方法的實現(xiàn)逛薇,如果沒有捺疼,元類會向他父類查找該方法。同時注意的是:元類(meteClass)也是類永罚,它也是對象啤呼。元類也有isa指針,它的isa指針最終指向的是一個根元類(root meteClass).根元類的isa指針指向本身,這樣形成了一個封閉的內(nèi)循環(huán)呢袱。
下面這張圖很好的解釋了類的繼承關(guān)系:

每一個對象本質(zhì)上都是一個類的實例官扣。其中類定義了成員變量和成員方法的列表。對象通過對象的isa指針指向類羞福。
每一個類本質(zhì)上都是一個對象惕蹄,類其實是元類(meteClass)的實例。元類定義了類方法的列表治专。類通過類的isa指針指向元類卖陵。
所有的元類最終繼承一個根元類,根元類isa指針指向本身张峰,形成一個封閉的內(nèi)循環(huán)泪蔫。
####第三部分:RunTime機制
RunTime指一個程序在運行(或者在被執(zhí)行)的環(huán)境。簡稱運行時喘批。它指的是系統(tǒng)在運行的時候的一些機制撩荣,其中最主要的是消息機制。
OC中RunTime本質(zhì)上是一套比較底層的純C語言API, 屬于1個C語言庫, 包含了很多底層的C語言API谤祖。 在我們平時編寫的OC代碼中, 程序運行過程時, 其實最終都是轉(zhuǎn)成了runtime的C語言代碼婿滓。RunTime提供了一些使得對象之間能夠傳遞消息的重要函數(shù)老速,并且包含了創(chuàng)建類實例所用的全部邏輯粥喜。
#####基礎(chǔ)知識:
在OC中,給某個對象傳遞消息可以這樣寫:
` id returnValue = [obj messageName:parameter];`
其中obj是接受對象,messageName是選擇子(selector),parameter是參數(shù)橘券。
我們把selector和parameter結(jié)合起來稱為消息额湘。
編譯器會把以上消息轉(zhuǎn)化為C語言函數(shù)調(diào)用:
`id returnValue = objc_msgSend(id obj,SEL messageName,parameter).`
`objc_msgSend`是消息傳遞機制中核心函數(shù)卿吐。其原型如下:
` void objc_msgSend(id self,SEL cmd,...).`
消息傳遞機制主要分為以下幾個階段:
#####第一階段:消息傳遞機制(pass a message)
編譯把OC中方法轉(zhuǎn)化objc_msgSend形式后。objc_msgSend函數(shù)首先通過接受對象(obj)的isa指針找到接收對象(obj)對應(yīng)的類(class)锋华。在類(Class)中先去cache中 通過選擇子(SEL)查找對應(yīng)函數(shù)的方法method()嗡官,若 cache中未找到。再去方法列表(methodList)中查找毯焕,若方法列表(methodList)中未找到衍腥,則去superClass中查找。若能找到纳猫,則將method加 入到cache中婆咸,以方便下次查找,并通過method中的函數(shù)指針跳轉(zhuǎn)到對應(yīng)的函數(shù)中去執(zhí)行芜辕。若沒有找到尚骄,消息傳遞機制將進入第二階段消息轉(zhuǎn)發(fā)階段(消息轉(zhuǎn)發(fā)機制)。
緩存:每一個類都都有一塊緩存(cache)侵续。其存儲的是“快速映射表”倔丈。objc_msgSend會將方法的匹配結(jié)果緩存在快速映射表中。
消息派發(fā)系統(tǒng)會將類的方法列表的名稱映射到相關(guān)方法的實現(xiàn)上状蜗。以此來找到應(yīng)該調(diào)用的方法需五。這些方法均以函數(shù)指針(IMP)的形式來表示。其原型是:
`id(*IMP)(id轧坎,SEL警儒,...)`
方法調(diào)配技術(shù):運行此特性可以改變相關(guān)方法的實現(xiàn)。
NSString 有可以響應(yīng)以下幾個方法:

OC運行系統(tǒng)提供了幾個方法能夠操控這張表眶根。開發(fā)者可以向其中新增選擇子,也可以改變選擇子的實現(xiàn)蜀铲,還可以交換選擇子所映射的指針。經(jīng)過幾次操作可以變成以下:

對比兩張表属百,我們可以發(fā)現(xiàn)下面新增加了一個選擇子记劝。交換兩個選擇子的實現(xiàn)∽迦牛可以看出方法調(diào)配技術(shù)的強大之處厌丑。
交換兩個方法的實現(xiàn),參數(shù)為方法的實現(xiàn)渔呵。
`void method_exchangeImplementations(Method m1,Method m2)`
得到方法的實現(xiàn):
` Method class_getInstanceMethod(Class aClsaa,SEL aSelector)`
實際應(yīng)用中交換兩個方法的實現(xiàn)意義不大怒竿。一般是在類別中新增加一個方法,然后交換兩個方法的實現(xiàn)扩氢。以此來改變類的方法的實現(xiàn)耕驰。

#####第二階段:消息轉(zhuǎn)發(fā)機制(message forwarding)
消息轉(zhuǎn)發(fā)機制分為兩個階段:
######1.動態(tài)方法解析:
第一階段運行系統(tǒng)會先征詢接受者所屬的類看其是否動態(tài)添加方法以處理當前的這個未知選擇子(SEL)。
在對象收到無法解讀的消息后录豺,便會調(diào)用所屬類的下列方法:
`+(BOOL)resolveInstanceMethod:(SEL)selector`
該方法參數(shù)就是需要處理的未知選擇子朦肘。返回值是表示這個類是否能新增一個方法處理這個未知選擇子饭弓。在處罰完整消息轉(zhuǎn)發(fā)機制前本類有機會增加一個方法處理這個未知選擇子。在此類方法中可以通過`class_addMethod` 增加一個方法處理未知選擇子媒抠。
如果是類方法將會觸發(fā)下面這個方法:
`+(BOOL)resolveClassMethod:(SEL)selector`
######2.完整的消息轉(zhuǎn)發(fā)機制:
如果第一階段沒有成功的處理未知選擇子弟断,那么接受者自己再也沒有辦法動態(tài)的添加方法來響應(yīng)該未知選擇子的消息了 。此時運行系統(tǒng)會啟動完整的消息轉(zhuǎn)發(fā)機制趴生。這里又分為兩個小階段:
**第一階段**:會請接受者看看有沒有其他對象能處理這條消息阀趴。若有,運行系統(tǒng)會把消息轉(zhuǎn)給那個對象(備援的接受者),消息轉(zhuǎn)發(fā)結(jié)束 苍匆。若沒有將進入第二階段舍咖。
`-(id)forwardingTargetForSelector:(SEL)selector`
在此方法中若當前接受者能找到備援對象,那將其返回锉桑。若找不到返回nil排霉,觸發(fā)下一個階段。
**第二階段**:運行系統(tǒng)會把消息的全部細節(jié)封裝到NSInvocation對象中民轴,再給接受者一次機會攻柠,另其解決當前未知選擇子(SEL)。
如果消息轉(zhuǎn)發(fā)到了這一個階段后裸,運行系統(tǒng)就會啟用完整的消息轉(zhuǎn)發(fā)機制瑰钮。首先創(chuàng)建NSInvocation對象。把消息的全部細節(jié)封裝于其中微驶。此對象包括選擇子浪谴,目標對象和參數(shù)。觸發(fā)NSInvocation對象時因苹,消息派發(fā)系統(tǒng)(message-dispathch-system)將親自把消息指派給目標對象苟耻。
此步驟運行系統(tǒng)會調(diào)用下面方法來處理:
` -(void)forwardingInvocation:(NSInvocation)invocation。`
這個方法可以有多種實現(xiàn)形式扶檐⌒渍龋可以很簡單,改變消息的調(diào)用目標款筑,使消息在新的目標上調(diào)用智蝠。這個備援接受者是相同的原理∧问幔可以為改變消息的內(nèi)容杈湾,參數(shù)。使其可以被目標對象接受攘须。也可以更換選擇子等漆撞。
消息轉(zhuǎn)發(fā)全流程:

####第四部分:RunTim的常見應(yīng)用場合