Runtime 淺談

Objective-C 的 Runtime 鑄就了它動(dòng)態(tài)語(yǔ)言的特性,這些深層次的知識(shí)雖然平時(shí)寫(xiě)代碼用的少一些狭郑,但是卻是每個(gè) Objc 程序員需要了解的。

簡(jiǎn)介

因?yàn)镺bjc是一門(mén)動(dòng)態(tài)語(yǔ)言,所以它總是想辦法把一些決定工作從編譯連接推遲到運(yùn)行時(shí)祝迂。也就是說(shuō)只有編譯器是不夠的,還需要一個(gè)運(yùn)行時(shí)系統(tǒng) (runtime system) 來(lái)執(zhí)行編譯后的代碼器净。這就是 Objective-C Runtime 系統(tǒng)存在的意義型雳,它是整個(gè)Objc運(yùn)行框架的一塊基石。

Runtime其實(shí)有兩個(gè)版本:“modern”和 “l(fā)egacy”山害。我們現(xiàn)在用的 Objective-C 2.0 采用的是現(xiàn)行(Modern)版的Runtime系統(tǒng)纠俭,只能運(yùn)行在 iOS 和 OS X 10.5 之后的64位程序中。而OS X較老的32位程序仍采用 Objective-C 1中的(早期)Legacy 版本的 Runtime 系統(tǒng)浪慌。這兩個(gè)版本最大的區(qū)別在于當(dāng)你更改一個(gè)類(lèi)的實(shí)例變量的布局時(shí)冤荆,在早期版本中你需要重新編譯它的子類(lèi),而現(xiàn)行版就不需要眷射。

Runtime基本是用C和匯編寫(xiě)的匙赞,可見(jiàn)蘋(píng)果為了動(dòng)態(tài)系統(tǒng)的高效而作出的努力。你可以在這里下到蘋(píng)果維護(hù)的開(kāi)源代碼妖碉。蘋(píng)果和GNU各自維護(hù)一個(gè)開(kāi)源的runtime版本涌庭,這兩個(gè)版本之間都在努力的保持一致。

與Runtime交互

Objc 從三種不同的層級(jí)上與 Runtime 系統(tǒng)進(jìn)行交互欧宜,分別是通過(guò) Objective-C 源代碼坐榆,通過(guò) Foundation 框架的NSObject類(lèi)定義的方法,通過(guò)對(duì) runtime 函數(shù)的直接調(diào)用冗茸。

Objective-C源代碼

大部分情況下你就只管寫(xiě)你的Objc代碼就行席镀,runtime 系統(tǒng)自動(dòng)在幕后辛勤勞作著。
還記得引言中舉的例子吧夏漱,消息的執(zhí)行會(huì)使用到一些編譯器為實(shí)現(xiàn)動(dòng)態(tài)語(yǔ)言特性而創(chuàng)建的數(shù)據(jù)結(jié)構(gòu)和函數(shù)豪诲,Objc中的類(lèi)、方法和協(xié)議等在 runtime 中都由一些數(shù)據(jù)結(jié)構(gòu)來(lái)定義挂绰,這些內(nèi)容在后面會(huì)講到屎篱。(比如objc_msgSend函數(shù)及其參數(shù)列表中的id和SEL都是啥)

NSObject的方法

Cocoa 中大多數(shù)類(lèi)都繼承于NSObject類(lèi),也就自然繼承了它的方法葵蒂。最特殊的例外是NSProxy交播,它是個(gè)抽象超類(lèi),它實(shí)現(xiàn)了一些消息轉(zhuǎn)發(fā)有關(guān)的方法践付,可以通過(guò)繼承它來(lái)實(shí)現(xiàn)一個(gè)其他類(lèi)的替身類(lèi)或是虛擬出一個(gè)不存在的類(lèi)秦士,說(shuō)白了就是領(lǐng)導(dǎo)把自己展現(xiàn)給大家風(fēng)光無(wú)限,但是把活兒都交給幕后小弟去干永高。

有的NSObject中的方法起到了抽象接口的作用隧土,比如description方法需要你重載它并為你定義的類(lèi)提供描述內(nèi)容提针。NSObject還有些方法能在運(yùn)行時(shí)獲得類(lèi)的信息,并檢查一些特性次洼,比如class返回對(duì)象的類(lèi)关贵;isKindOfClass:和isMemberOfClass:則檢查對(duì)象是否在指定的類(lèi)繼承體系中;respondsToSelector:檢查對(duì)象能否響應(yīng)指定的消息卖毁;conformsToProtocol:檢查對(duì)象是否實(shí)現(xiàn)了指定協(xié)議類(lèi)的方法;methodForSelector:則返回指定方法實(shí)現(xiàn)的地址落萎。

Runtime的函數(shù)

Runtime 系統(tǒng)是一個(gè)由一系列函數(shù)和數(shù)據(jù)結(jié)構(gòu)組成亥啦,具有公共接口的動(dòng)態(tài)共享庫(kù)。頭文件存放于/usr/include/objc目錄下练链。許多函數(shù)允許你用純C代碼來(lái)重復(fù)實(shí)現(xiàn) Objc 中同樣的功能翔脱。雖然有一些方法構(gòu)成了NSObject類(lèi)的基礎(chǔ),但是你在寫(xiě) Objc 代碼時(shí)一般不會(huì)直接用到這些函數(shù)的媒鼓,除非是寫(xiě)一些 Objc 與其他語(yǔ)言的橋接或是底層的debug工作届吁。在Objective-C Runtime Reference中有對(duì) Runtime 函數(shù)的詳細(xì)文檔。

Runtime術(shù)語(yǔ)

消息發(fā)送的objc_msgSend:方法吧绿鸣,它的真身是這樣的:
id objc_msgSend ( id self, SEL op, ... );
下面將會(huì)逐漸展開(kāi)介紹一些術(shù)語(yǔ)疚沐,其實(shí)它們都對(duì)應(yīng)著數(shù)據(jù)結(jié)構(gòu)。

SEL

objc_msgSend函數(shù)第二個(gè)參數(shù)類(lèi)型為SEL潮模,它是selector在Objc中的表示類(lèi)型(Swift中是Selector類(lèi))亮蛔。selector是方法選擇器,可以理解為區(qū)分方法的 ID擎厢,而這個(gè) ID 的數(shù)據(jù)結(jié)構(gòu)是SEL:
typedef struct objc_selector *SEL;
其實(shí)它就是個(gè)映射到方法的C字符串究流,你可以用 Objc 編譯器命令@selector()或者 Runtime 系統(tǒng)的sel_registerName函數(shù)來(lái)獲得一個(gè)SEL類(lèi)型的方法選擇器。

不同類(lèi)中相同名字的方法所對(duì)應(yīng)的方法選擇器是相同的动遭,即使方法名字相同而變量類(lèi)型不同也會(huì)導(dǎo)致它們具有相同的方法選擇器芬探,于是 Objc 中方法命名有時(shí)會(huì)帶上參數(shù)類(lèi)型(NSNumber一堆抽象工廠方法拿走不謝),Cocoa 中有好多長(zhǎng)長(zhǎng)的方法哦厘惦。

id

objc_msgSend第一個(gè)參數(shù)類(lèi)型為id偷仿,大家對(duì)它都不陌生,它是一個(gè)指向類(lèi)實(shí)例的指針:
typedef struct objc_object *id;
那objc_object又是啥呢:
struct objc_object { Class isa; };
objc_object結(jié)構(gòu)體包含一個(gè)isa指針绵估,根據(jù)isa指針就可以順藤摸瓜找到對(duì)象所屬的類(lèi)炎疆。

Class

之所以說(shuō)isa是指針是因?yàn)镃lass其實(shí)是一個(gè)指向objc_class結(jié)構(gòu)體的指針:
typedef struct objc_class *Class;
而objc_class就是我們摸到的那個(gè)瓜,里面的東西多著呢:

struct objc_class {
    Class isa OBJC_ISA_AVAILABILITY; //isa指針指向Meta Class国裳,因?yàn)镺bjc的類(lèi)的本身也是一個(gè)Object形入,為了處理這個(gè)關(guān)系,r       untime就創(chuàng)造了Meta Class缝左,當(dāng)給類(lèi)發(fā)送[NSObject alloc]這樣消息時(shí)亿遂,實(shí)際上是把這個(gè)消息發(fā)給了Class Object

    #if !__OBJC2__
    Class super_class OBJC2_UNAVAILABLE; // 父類(lèi)
    const char *name OBJC2_UNAVAILABLE; // 類(lèi)名
    long version OBJC2_UNAVAILABLE; // 類(lèi)的版本信息浓若,默認(rèn)為0
    long info OBJC2_UNAVAILABLE; // 類(lèi)信息,供運(yùn)行期使用的一些位標(biāo)識(shí)
    long instance_size OBJC2_UNAVAILABLE; // 該類(lèi)的實(shí)例變量大小
    struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 該類(lèi)的成員變量鏈表
    struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定義的鏈表
    struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法緩存蛇数,對(duì)象接到一個(gè)消息會(huì)根據(jù)isa指針查找消息對(duì)象挪钓,這時(shí)會(huì)在method       Lists中遍歷,如果cache了耳舅,常用的方法調(diào)用時(shí)就能夠提高調(diào)用的效率碌上。
    struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 協(xié)議鏈表
    #endif

} OBJC2_UNAVAILABLE;

可以看到運(yùn)行時(shí)一個(gè)類(lèi)還關(guān)聯(lián)了它的超類(lèi)指針,類(lèi)名浦徊,成員變量馏予,方法,緩存盔性,還有附屬的協(xié)議霞丧。
其中objc_ivar_list和objc_method_list分別是成員變量列表和方法列表:

struct objc_ivar_list {
    int ivar_count                                           OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_ivar ivar_list[1]                            OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;
 
struct objc_method_list {
    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;
 
    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}

如果你C語(yǔ)言不是特別好,可以直接理解為objc_ivar_list結(jié)構(gòu)體存儲(chǔ)著objc_ivar數(shù)組列表冕香,而objc_ivar結(jié)構(gòu)體存儲(chǔ)了類(lèi)的單個(gè)成員變量的信息蛹尝;同理objc_method_list結(jié)構(gòu)體存儲(chǔ)著objc_method數(shù)組列表,而objc_method結(jié)構(gòu)體存儲(chǔ)了類(lèi)的某個(gè)方法的信息悉尾。

最后要提到的還有一個(gè)objc_cache突那,顧名思義它是緩存,它在objc_class的作用很重要焕襟,在后面會(huì)講到陨收。

一個(gè) ObjC 類(lèi)同時(shí)也是一個(gè)對(duì)象,為了處理類(lèi)和對(duì)象的關(guān)系鸵赖,runtime 庫(kù)創(chuàng)建了一種叫做元類(lèi) (Meta Class) 的東西务漩。當(dāng)你發(fā)出一個(gè)類(lèi)似[NSObject alloc]的消息時(shí),你事實(shí)上是把這個(gè)消息發(fā)給了一個(gè)類(lèi)對(duì)象 (Class Object) ,這個(gè)類(lèi)對(duì)象必須是一個(gè)元類(lèi)的實(shí)例,而這個(gè)元類(lèi)同時(shí)也是一個(gè)根元類(lèi) (root meta class) 的實(shí)例悦荒。你會(huì)說(shuō) NSObject 的子類(lèi)時(shí),你的類(lèi)就會(huì)指向 NSObject 做為其超類(lèi)居触。但是所有的元類(lèi)最終都指向根元類(lèi)為其超類(lèi)。所有的元類(lèi)的方法列表都有能夠響應(yīng)消息的類(lèi)方法老赤。所以當(dāng) [NSObject alloc] 這條消息發(fā)給類(lèi)對(duì)象的時(shí)候轮洋,objc_msgSend()會(huì)去它的元類(lèi)里面去查找能夠響應(yīng)消息的方法,如果找到了抬旺,然后對(duì)這個(gè)類(lèi)對(duì)象執(zhí)行方法調(diào)用弊予。


image

圖實(shí)線(xiàn)是 super_class 指針,虛線(xiàn)是isa指針开财。 有趣的是根元類(lèi)的超類(lèi)是NSObject汉柒,而isa指向了自己误褪,而NSObject的超類(lèi)為nil,也就是它沒(méi)有超類(lèi)碾褂。

Method

Method是一種代表類(lèi)中的某個(gè)方法的類(lèi)型兽间。
typedef struct objc_method *Method;
而objc_method在上面的方法列表中提到過(guò),它存儲(chǔ)了方法名正塌,方法類(lèi)型和方法實(shí)現(xiàn):

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}
  • 方法名類(lèi)型為SEL嘀略,前面提到過(guò)相同名字的方法即使在不同類(lèi)中定義,它們的方法選擇器也相同传货。
  • 方法類(lèi)型method_types是個(gè)char指針屎鳍,其實(shí)存儲(chǔ)著方法的參數(shù)類(lèi)型和返回值類(lèi)型。
  • method_imp指向了方法的實(shí)現(xiàn)问裕,本質(zhì)上是一個(gè)函數(shù)指針,后面會(huì)詳細(xì)講到孵坚。

Ivar

Ivar是一種代表類(lèi)中實(shí)例變量的類(lèi)型粮宛。
typedef struct objc_ivar *Ivar;
而objc_ivar在上面的成員變量列表中也提到過(guò):

struct objc_ivar {
    char *ivar_name                                          OBJC2_UNAVAILABLE;
    char *ivar_type                                          OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}

PS:OBJC2_UNAVAILABLE之類(lèi)的宏定義是蘋(píng)果在 Objc 中對(duì)系統(tǒng)運(yùn)行版本進(jìn)行約束的黑魔法,有興趣的可以查看源代碼卖宠。

IMP

IMP在objc.h中的定義是:
typedef id (*IMP)(id, SEL, ...);
它就是一個(gè)函數(shù)指針巍杈,這是由編譯器生成的。當(dāng)你發(fā)起一個(gè) ObjC 消息之后扛伍,最終它會(huì)執(zhí)行的那段代碼筷畦,就是由這個(gè)函數(shù)指針指定的。而IMP這個(gè)函數(shù)指針就指向了這個(gè)方法的實(shí)現(xiàn)刺洒。既然得到了執(zhí)行某個(gè)實(shí)例某個(gè)方法的入口鳖宾,我們就可以繞開(kāi)消息傳遞階段,直接執(zhí)行方法逆航,這在后面會(huì)提到鼎文。

你會(huì)發(fā)現(xiàn)IMP指向的方法與objc_msgSend函數(shù)類(lèi)型相同,參數(shù)都包含idSEL類(lèi)型因俐。每個(gè)方法名都對(duì)應(yīng)一個(gè)SEL類(lèi)型的方法選擇器拇惋,而每個(gè)實(shí)例對(duì)象中的SEL對(duì)應(yīng)的方法實(shí)現(xiàn)肯定是唯一的,通過(guò)一組idSEL參數(shù)就能確定唯一的方法實(shí)現(xiàn)地址抹剩;反之亦然撑帖。

Cache

在runtime.h中Cache的定義如下:
typedef struct objc_cache *Cache
還記得之前objc_class結(jié)構(gòu)體中有一個(gè)struct objc_cache *cache吧,它到底是緩存啥的呢澳眷,先看看objc_cache的實(shí)現(xiàn):

struct objc_cache {
    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
    Method buckets[1]                                        OBJC2_UNAVAILABLE;
};

Cache為方法調(diào)用的性能進(jìn)行優(yōu)化胡嘿,通俗地講,每當(dāng)實(shí)例對(duì)象接收到一個(gè)消息時(shí)境蔼,它不會(huì)直接在isa指向的類(lèi)的方法列表中遍歷查找能夠響應(yīng)消息的方法灶平,因?yàn)檫@樣效率太低了伺通,而是優(yōu)先在Cache中查找。Runtime 系統(tǒng)會(huì)把被調(diào)用的方法存到Cache中(理論上講一個(gè)方法如果被調(diào)用逢享,那么它有可能今后還會(huì)被調(diào)用)罐监,下次查找的時(shí)候效率更高。這根計(jì)算機(jī)組成原理中學(xué)過(guò)的 CPU 繞過(guò)主存先訪問(wèn)Cache的道理挺像瞒爬,而我猜蘋(píng)果為提高Cache命中率應(yīng)該也做了努力吧弓柱。

這篇介紹先到這里 關(guān)于消息發(fā)送的有關(guān)理解請(qǐng)看下一篇文章 <<Runtime --- > 消息發(fā)送>>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市侧但,隨后出現(xiàn)的幾起案子矢空,更是在濱河造成了極大的恐慌,老刑警劉巖禀横,帶你破解...
    沈念sama閱讀 221,273評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屁药,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡柏锄,警方通過(guò)查閱死者的電腦和手機(jī)酿箭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)趾娃,“玉大人缭嫡,你說(shuō)我怎么就攤上這事√疲” “怎么了妇蛀?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,709評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)笤成。 經(jīng)常有香客問(wèn)我评架,道長(zhǎng),這世上最難降的妖魔是什么疹启? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,520評(píng)論 1 296
  • 正文 為了忘掉前任古程,我火速辦了婚禮,結(jié)果婚禮上喊崖,老公的妹妹穿的比我還像新娘挣磨。我一直安慰自己,他們只是感情好荤懂,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布茁裙。 她就那樣靜靜地躺著,像睡著了一般节仿。 火紅的嫁衣襯著肌膚如雪晤锥。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,158評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音矾瘾,去河邊找鬼女轿。 笑死,一個(gè)胖子當(dāng)著我的面吹牛壕翩,可吹牛的內(nèi)容都是我干的蛉迹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,755評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼放妈,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼北救!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起芜抒,我...
    開(kāi)封第一講書(shū)人閱讀 39,660評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤珍策,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后宅倒,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體攘宙,經(jīng)...
    沈念sama閱讀 46,203評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評(píng)論 3 340
  • 正文 我和宋清朗相戀三年拐迁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了模聋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,427評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡唠亚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出持痰,到底是詐尸還是另有隱情灶搜,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評(píng)論 5 349
  • 正文 年R本政府宣布工窍,位于F島的核電站割卖,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏患雏。R本人自食惡果不足惜鹏溯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望淹仑。 院中可真熱鬧丙挽,春花似錦、人聲如沸匀借。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,272評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)吓肋。三九已至凳怨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背肤舞。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工紫新, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人李剖。 一個(gè)月前我還...
    沈念sama閱讀 48,808評(píng)論 3 376
  • 正文 我出身青樓芒率,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親杖爽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子敲董,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評(píng)論 2 359

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