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)建的,可以有無數(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)體凛剥,來看一下 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ā)

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

交換兩個(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 來注冊(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)系作者
  • 序言:七十年代末韵卤,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子崇猫,更是在濱河造成了極大的恐慌沈条,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诅炉,死亡現(xiàn)場(chǎng)離奇詭異蜡歹,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)涕烧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門月而,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人议纯,你說我怎么就攤上這事父款。” “怎么了瞻凤?”我有些...
    開封第一講書人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵憨攒,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我阀参,道長(zhǎng)肝集,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任蛛壳,我火速辦了婚禮包晰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘炕吸。我一直安慰自己伐憾,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開白布赫模。 她就那樣靜靜地躺著树肃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瀑罗。 梳的紋絲不亂的頭發(fā)上胸嘴,一...
    開封第一講書人閱讀 49,821評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音斩祭,去河邊找鬼劣像。 笑死,一個(gè)胖子當(dāng)著我的面吹牛摧玫,可吹牛的內(nèi)容都是我干的耳奕。 我是一名探鬼主播,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼屋群!你這毒婦竟也來了闸婴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤芍躏,失蹤者是張志新(化名)和其女友劉穎邪乍,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體对竣,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡庇楞,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了否纬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片姐刁。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖烦味,靈堂內(nèi)的尸體忽然破棺而出聂使,到底是詐尸還是另有隱情,我是刑警寧澤谬俄,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布柏靶,位于F島的核電站,受9級(jí)特大地震影響溃论,放射性物質(zhì)發(fā)生泄漏屎蜓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一钥勋、第九天 我趴在偏房一處隱蔽的房頂上張望炬转。 院中可真熱鬧,春花似錦算灸、人聲如沸扼劈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)荐吵。三九已至,卻和暖如春赊瞬,著一層夾襖步出監(jiān)牢的瞬間先煎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工巧涧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留薯蝎,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓谤绳,卻偏偏與公主長(zhǎng)得像占锯,于是被迫代替她去往敵國(guó)和親袒哥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉烟央,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,690評(píng)論 0 9
  • 我們常常會(huì)聽說 Objective-C 是一門動(dòng)態(tài)語(yǔ)言统诺,那么這個(gè)「動(dòng)態(tài)」表現(xiàn)在哪呢歪脏?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,182評(píng)論 0 7
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,548評(píng)論 33 466
  • Runtime是什么 Runtime 又叫運(yùn)行時(shí)疑俭,是一套底層的 C 語(yǔ)言 API,其為 iOS 內(nèi)部的核心之一婿失,我...
    SuAdrenine閱讀 871評(píng)論 0 3
  • Frentiu FD, et al. (2008) Pedigree-free animal models: th...
    董八七閱讀 529評(píng)論 0 0