iOS 常見知識(shí)點(diǎn)(一):Runtime

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è)。

消息分發(fā)

在 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ā)

消息轉(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ì)象就可以模擬多繼承的效果。

交換兩個(gè)方法的實(shí)現(xiàn)

    Method m1 = class_getInstanceMethod([M1 class], @selector(hello1));
    Method m2 = class_getInstanceMethod([M2 class], @selector(hello2));
    method_exchangeImplementations(m2, m1);

關(guān)聯(lián)對(duì)象

通過下面兩個(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)。

將 json 轉(zhuǎn)換為 model

用到了 Runtime 獲取某一個(gè)類的全部屬性的名字,以及 Runtime 獲取屬性的類型。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,273評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,527評(píng)論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,010評(píng)論 1 322
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,250評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,769評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,656評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,853評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,103評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評(píng)論 1 281
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,487評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,815評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容