Runtime
Runtime 是一個(gè)運(yùn)行時(shí)庫(kù),主要使用 C 和匯編寫的庫(kù),為 C 添加了面向?qū)ο蟮哪芰Σ?chuàng)造了 Objective-C,并且擁有消息分發(fā),消息轉(zhuǎn)發(fā)等功能。
也就是 Runtime 涉及三個(gè)點(diǎn),面向?qū)ο螅⒎职l(fā),消息轉(zhuǎn)發(fā)。
面向?qū)ο螅?/strong>
Objective-C 的對(duì)象是基于 Runtime 創(chuàng)建的結(jié)構(gòu)體。先從代碼層面分析一下。
Class *class = [[Class alloc] init];
alloc 方法會(huì)為對(duì)象分配一塊內(nèi)存空間,空間的大小為 isa_t(8 字節(jié))的大小加上所有成員變量所需的空間,再進(jìn)行一次內(nèi)存對(duì)齊。分配完空間后會(huì)初始化 isa_t ,而 isa_t 是一個(gè) union 類型的結(jié)構(gòu)體(或者稱之為聯(lián)合體),它的結(jié)構(gòu)是在 Runtime 里被定義的。
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
struct {
uintptr_t indexed : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33;
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19;
};
};
從 isa_t 的結(jié)構(gòu)可以看出,isa_t 可以存儲(chǔ) struct,uintptr_t 或者 Class 類型
。
init 方法就直接返回了初始化好的對(duì)象,class 指針指向這個(gè)初始化好的對(duì)象。
也就是在 Runtime 的協(xié)助之下,一個(gè)對(duì)象完成了創(chuàng)建。
你可能想知道,這個(gè)對(duì)象只存放了一個(gè) isa_t 結(jié)構(gòu)體和成員變量,對(duì)象的方法在哪里?
在編譯的時(shí)候,類在內(nèi)存中的位置就已經(jīng)確定,而在 main 方法之前,Runtime 將可執(zhí)行文件中和動(dòng)態(tài)庫(kù)所有的符號(hào)(Class,Protocol,Selector,IMP,…)加載到內(nèi)存中,由 Runtime 管理,這里也包括了也是一個(gè)對(duì)象的類。
類對(duì)象里儲(chǔ)存著一個(gè) isa_t 的結(jié)構(gòu)體,super_class 指針,cache_t 結(jié)構(gòu)體,class_data_bits_t 指針。
struct objc_class : objc_object {
isa_t isa;
Class superclass;
cache_t cache;
class_data_bits_t bits;
}
class_data_bits_t 指向類對(duì)象的數(shù)據(jù)區(qū)域,數(shù)據(jù)區(qū)域存放著這個(gè)類的實(shí)例方法鏈表。而類方法存在元類對(duì)象的數(shù)據(jù)區(qū)域。也就是有對(duì)象,類對(duì)象,元類對(duì)象三個(gè)概念,對(duì)象是在運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建的,可以有無(wú)數(shù)個(gè),類對(duì)象和元類對(duì)象在 main 方法之前創(chuàng)建的,分別只會(huì)有一個(gè)。
在 Objective-C 中的“方法調(diào)用”其實(shí)應(yīng)該叫做消息傳遞,[object message] 會(huì)被編譯器翻譯為 objc_msgSend(object, @selector(message)),這是一個(gè) C 方法,首先看它的兩個(gè)參數(shù),第一個(gè)是 object ,既方法調(diào)用者,第二個(gè)參數(shù)稱為選擇子 SEL,Objective-C 為我們維護(hù)了一個(gè)巨大的選擇子表,在使用 @selector() 時(shí)會(huì)從這個(gè)選擇子表中根據(jù)選擇子的名字查找對(duì)應(yīng)的 SEL。如果沒有找到,則會(huì)生成一個(gè) SEL 并添加到表中,在編譯期間會(huì)掃描全部的頭文件和實(shí)現(xiàn)文件將其中的方法以及使用 @selector() 生成的選擇子加入到選擇子表中。
通過第一個(gè)參數(shù) object,可以找到 object 對(duì)象的 isa_t 結(jié)構(gòu)體,從上文中能看 isa_t 結(jié)構(gòu)體的結(jié)構(gòu),在 isa_t 結(jié)構(gòu)體中,shiftcls 存放的是一個(gè) 33 位的地址,用于指向 object 對(duì)象的類對(duì)象,而類對(duì)象里有一個(gè) cache_t 結(jié)構(gòu)體,來(lái)看一下 cache_t 的具體代碼,
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
}
_mask:分配用來(lái)緩存 bucket 的總數(shù)。
_occupied:表明目前實(shí)際占用的緩存 bucket 的個(gè)數(shù)。
_buckets:一個(gè)散列表,用來(lái)方法緩存,bucket_t 類型,包含 key 以及方法實(shí)現(xiàn) IMP。
struct bucket_t {
private:
cache_key_t _key;
IMP _imp;
}
objc_msgSend() 方法會(huì)先從緩存表里,查找是否有該 SEL 對(duì)應(yīng)的 IMP,有的話算命中緩存,直接通過函數(shù)指針 IMP ,找到方法的具體實(shí)現(xiàn)函數(shù),執(zhí)行。
當(dāng)然緩存表里可能并不會(huì)命中,則此時(shí)會(huì)根據(jù)類對(duì)象的 class_data_bits_t 指針找到數(shù)據(jù)區(qū)域,數(shù)據(jù)區(qū)域里用鏈表存放著類的實(shí)例方法,實(shí)例方法也是一個(gè)結(jié)構(gòu)體,其結(jié)構(gòu)為:
struct method_t {
SEL name;
const char *types;
IMP imp;
};
編譯器將每個(gè)方法的返回值和參數(shù)類型編碼為一個(gè)字符串,types 指向的就是這樣一個(gè)字符串,objc_msgSend() 會(huì)在類對(duì)象的方法鏈表里按鏈表順序去匹配 SEL,匹配成功則停止,并將此方法加入到類對(duì)象的 _buckets 里緩存起來(lái)。如果沒找到則會(huì)通過類對(duì)象的 superclass 指針找到其父類,去父類的方法列表里尋找(也會(huì)從父類的方法緩存列表開始)。
如果繼續(xù)沒有找到會(huì)一直向父類尋找,直到遇見 NSObject,NSObject 的 superclass 指向 nil。也就意味著尋找結(jié)束,并沒有找到實(shí)現(xiàn)方法。(如果這個(gè)過程找到了,也同樣會(huì)在 object 的類對(duì)象的 _buckets 里緩存起來(lái))。
選擇子在當(dāng)前類和父類中都沒有找到實(shí)現(xiàn),就進(jìn)入了方法決議(method resolve),首先判斷當(dāng)前 object 的類對(duì)象是否實(shí)現(xiàn)了 resolveInstanceMethod: 方法,如果實(shí)現(xiàn)的話,會(huì)調(diào)用 resolveInstanceMethod:方法,這個(gè)時(shí)候我們可以在 resolveInstanceMethod:方法里動(dòng)態(tài)的添加該 SEL 對(duì)應(yīng)的方法(也可以去做點(diǎn)別的,比如寫入日志)。之后會(huì)重新執(zhí)行查找方法實(shí)現(xiàn)的流程,如果依舊沒找到方法,或者沒有實(shí)現(xiàn) resolveInstanceMethod: 方法,Runtime 還有另一套機(jī)制,消息轉(zhuǎn)發(fā)。
消息轉(zhuǎn)發(fā)分為以下幾步:
1.調(diào)用 forwardingTargetForSelector: 方法,嘗試找到一個(gè)能響應(yīng)該消息的對(duì)象。如果獲取到,則直接轉(zhuǎn)發(fā)給它。如果返回了 nil,繼續(xù)下面的動(dòng)作。
2.調(diào)用 methodSignatureForSelector: 方法,嘗試獲得一個(gè)方法簽名。如果獲取不到,則直接調(diào)用 doesNotRecognizeSelector 拋出異常。
3.調(diào)用 forwardInvocation: 方法,將第 2 步獲取到的方法簽名包裝成 Invocation 傳入,如何處理就在這里面了。
以上三個(gè)方法都可以通過在 object 的類對(duì)象里實(shí)現(xiàn), forwardingTargetForSelector: 可以通過對(duì)參數(shù) SEL 的判斷,返回一個(gè)可以響應(yīng)該消息的對(duì)象。這樣則會(huì)重新從該對(duì)象開始執(zhí)行查找方法實(shí)現(xiàn)的流程,找到了也同樣會(huì)在 object 的類對(duì)象的 _buckets 里緩存起來(lái)。而 2,3 方法則一般是配套使用,實(shí)現(xiàn) methodSignatureForSelector: 方法根據(jù)參數(shù) SEL ,做相應(yīng)處理,返回 NSMethodSignature (方法簽名) 對(duì)象,NSMethodSignature 對(duì)象會(huì)被包裝成 NSInvocation 對(duì)象,forwardInvocation: 方法里就可以對(duì) NSInvocation 進(jìn)行處理了。
上面是講的是實(shí)例方法,類方法沒什么區(qū)別,類方法儲(chǔ)存在元類對(duì)象的數(shù)據(jù)區(qū)域里,通過類對(duì)象的 isa_t 找到元類對(duì)象,執(zhí)行查找方法實(shí)現(xiàn)的流程,元類對(duì)象的 superclass 最終也會(huì)指向 NSObject。沒找到的話,也會(huì)有方法決議以及消息轉(zhuǎn)發(fā)。
runtime 可以做什么:
實(shí)現(xiàn)多繼承:從 forwardingTargetForSelector: 方法就能知道,一個(gè)類可以做到繼承多個(gè)類的效果,只需要在這一步將消息轉(zhuǎn)發(fā)給正確的類對(duì)象就可以模擬多繼承的效果。
Method m1 = class_getInstanceMethod([M1 class], @selector(hello1));
Method m2 = class_getInstanceMethod([M2 class], @selector(hello2));
method_exchangeImplementations(m2, m1);
通過下面兩個(gè)方法,可以給 category 實(shí)現(xiàn)添加成員變量的效果。
objc_setAssociatedObject
objc_getAssociatedObject
動(dòng)態(tài)添加類和方法:
objc_allocateClassPair 函數(shù)與 objc_registerClassPair 函數(shù)可以完成一個(gè)新類的添加,class_addMethod 給類添加方法,class_addIvar 添加成員變量,objc_registerClassPair 來(lái)注冊(cè)類,其中成員變量的添加必須在類注冊(cè)之前,類注冊(cè)后就可以創(chuàng)建該類的對(duì)象了,而再添加成員變量就會(huì)破壞創(chuàng)建的對(duì)象的內(nèi)存結(jié)構(gòu)。
用到了 Runtime 獲取某一個(gè)類的全部屬性的名字,以及 Runtime 獲取屬性的類型。