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)建的,可以有無數(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)體凛剥,來看一下 cache_t 的具體代碼侠仇,
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
}
_mask:分配用來緩存 bucket 的總數(shù)轻姿。
_occupied:表明目前實(shí)際占用的緩存 bucket 的個(gè)數(shù)。
_buckets:一個(gè)散列表逻炊,用來方法緩存互亮,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 里緩存起來。如果沒找到則會(huì)通過類對(duì)象的 superclass 指針找到其父類景图,去父類的方法列表里尋找(也會(huì)從父類的方法緩存列表開始)较雕。
如果繼續(xù)沒有找到會(huì)一直向父類尋找,直到遇見 NSObject挚币,NSObject 的 superclass 指向 nil郎笆。也就意味著尋找結(jié)束,并沒有找到實(shí)現(xiàn)方法忘晤。(如果這個(gè)過程找到了宛蚓,也同樣會(huì)在 object 的類對(duì)象的 _buckets 里緩存起來)。
選擇子在當(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 里緩存起來禁熏。而 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 來注冊(cè)類偏化,其中成員變量的添加必須在類注冊(cè)之前脐恩,類注冊(cè)后就可以創(chuàng)建該類的對(duì)象了镐侯,而再添加成員變量就會(huì)破壞創(chuàng)建的對(duì)象的內(nèi)存結(jié)構(gòu)。
用到了 Runtime 獲取某一個(gè)類的全部屬性的名字,以及 Runtime 獲取屬性的類型苟翻。