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)用

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
[7] Objective-C 與 Runtime:為什么是這樣?
http://www.itdecent.cn/p/ab966e8a82e2