用runtime看OC中類與對象罪帖,消息的底層實(shí)現(xiàn)原理

1.OC的類和對象

先看一下類和對象的數(shù)據(jù)結(jié)構(gòu):

typedef struct objc_class *Class;//類對象

struct objc_class {//類對象的結(jié)構(gòu)體

Class isa? OBJC_ISA_AVAILABILITY;//meta-class里存儲類方法促煮,meta-class的isa指向根類,super_class指向父類的meta-class整袁。

#if !__OBJC2__

Class super_class? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

const char *name? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

long version? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

long info? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

long instance_size? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

struct objc_ivar_list *ivars? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

struct objc_method_list **methodLists? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

struct objc_cache *cache? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

struct objc_protocol_list *protocols? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

#endif

} OBJC2_UNAVAILABLE;

struct objc_object {//實(shí)例對象結(jié)構(gòu)體

Class isa? OBJC_ISA_AVAILABILITY;

};

typedef struct objc_object *id;//實(shí)例對象

每個(gè)對象都會有一個(gè)它所屬的類菠齿。這是面向?qū)ο蟮幕靖拍睿窃贠C中坐昙,這對所有數(shù)據(jù)結(jié)構(gòu)有效绳匀。任何數(shù)據(jù)結(jié)構(gòu),只要在恰當(dāng)?shù)奈恢镁哂幸粋€(gè)指針指向一個(gè)class炸客,那么疾棵,它都可以被認(rèn)為是一個(gè)對象。objc_class和objc_object的struct指針都能稱為對象痹仙,我們?yōu)榱藚^(qū)分可以把它們分別叫做類對象和實(shí)例對象是尔,其中實(shí)例對象的isa指針指向類對象。

在OC中蝶溶,一個(gè)對象所屬于哪個(gè)類嗜历,是由它的isa指針指向的。這個(gè)isa指針指向這個(gè)對象所屬的class抖所。

我們看一下類對象的結(jié)構(gòu)體objc_class:Class isa指向的就是metaclass梨州,當(dāng)我們向一個(gè)類發(fā)送消息的時(shí)候,就是調(diào)用metaclass里面的方法田轧,如[NSString defaultStringEncoding]暴匠,Class super_class指向父類。同樣metaclass的結(jié)構(gòu)體也是objc_class傻粘,它的Class isa指向直接指向根類的metaclass每窖,根類的metaclass則是指向自身;它的Class super_class指向父類的metaclass弦悉;super_class的結(jié)構(gòu)體也是objc_class窒典,它的Class isa指向也是直接指向根類的metaclass,如果父類也有一個(gè)父類稽莉,它的Class super_class就是想父類瀑志。

類對象的結(jié)構(gòu)體objc_class里面用Class super_class指向父類是為了實(shí)現(xiàn)繼承;Class isa指向的就是metaclass則是為了實(shí)現(xiàn)向類發(fā)消息(也就是類方法)污秆,同時(shí)讓類和對象有了區(qū)分劈猪。


完美閉環(huán)


為了驗(yàn)證以上所述,我新建一個(gè)類Yordles繼承NSObject良拼,然后再新建一個(gè)類Timor繼承Yordles战得,用clang rewrite成c的實(shí)現(xiàn)

Timor類截取主要代碼如下:

static void OBJC_CLASS_SETUP_$_Timor(void ) {

OBJC_METACLASS_$_Timor.isa = &OBJC_METACLASS_$_NSObject;

OBJC_METACLASS_$_Timor.superclass = &OBJC_METACLASS_$_Yordles;

OBJC_METACLASS_$_Timor.cache = &_objc_empty_cache;

OBJC_CLASS_$_Timor.isa = &OBJC_METACLASS_$_Timor;

OBJC_CLASS_$_Timor.superclass = &OBJC_CLASS_$_Yordles;

OBJC_CLASS_$_Timor.cache = &_objc_empty_cache;

}

Yordles類截取相關(guān)代碼如下:

static void OBJC_CLASS_SETUP_$_Yordles(void ) {

OBJC_METACLASS_$_Yordles.isa = &OBJC_METACLASS_$_NSObject;

OBJC_METACLASS_$_Yordles.superclass = &OBJC_METACLASS_$_NSObject;

OBJC_METACLASS_$_Yordles.cache = &_objc_empty_cache;

OBJC_CLASS_$_Yordles.isa = &OBJC_METACLASS_$_Yordles;

OBJC_CLASS_$_Yordles.superclass = &OBJC_CLASS_$_NSObject;

OBJC_CLASS_$_Yordles.cache = &_objc_empty_cache;

}

2.objc_msgSend

在我們傳消息的時(shí)候(比如[NSString defaultStringEncoding],[timor wait])庸推,在c層面一般都會轉(zhuǎn)化成objc_msgSend方法常侦。在討論objc_msgSend的工作原理前,我們先看幾個(gè)結(jié)構(gòu)體

Method

Method表示類中的某個(gè)方法予弧,在runtime.h文件中找到它的定義:

/// An opaque type that represents a method in a class definition.

typedef struct objc_method *Method;

struct objc_method {

SEL method_name? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

char *method_types? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

IMP method_imp? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

}

其實(shí)Method就是一個(gè)指向objc_method結(jié)構(gòu)體指針刮吧,它存儲了方法名(method_name)、方法類型(method_types)和方法實(shí)現(xiàn)(method_imp)等信息掖蛤。而method_imp的數(shù)據(jù)類型是IMP杀捻,它是一個(gè)函數(shù)指針,后面會重點(diǎn)提及蚓庭。

Ivar

Ivar表示類中的實(shí)例變量致讥,在runtime.h文件中找到它的定義:

/// An opaque type that represents an instance variable.

typedef struct objc_ivar *Ivar;

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

}

Ivar其實(shí)就是一個(gè)指向objc_ivar結(jié)構(gòu)體指針,它包含了變量名(ivar_name)器赞、變量類型(ivar_type)等信息垢袱。

IMP

在上面講Method時(shí)就說過,IMP本質(zhì)上就是一個(gè)函數(shù)指針港柜,指向方法的實(shí)現(xiàn)请契,在objc.h找到它的定義:

/// A pointer to the function of a method implementation.

#if !OBJC_OLD_DISPATCH_PROTOTYPES

typedef void (*IMP)(void /* id, SEL, ... */ );

#else

typedef id (*IMP)(id, SEL, ...);

#endif

當(dāng)你向某個(gè)對象發(fā)送一條信息咳榜,可以由這個(gè)函數(shù)指針來指定方法的實(shí)現(xiàn),它最終就會執(zhí)行那段代碼爽锥,這樣可以繞開消息傳遞階段而去執(zhí)行另一個(gè)方法實(shí)現(xiàn)涌韩。

Cache

顧名思義,Cache主要用來緩存氯夷,那它緩存什么呢臣樱?我們先在runtime.h文件看看它的定義:

typedef struct objc_cache *Cache? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

struct objc_cache {

unsigned int mask /* total = mask + 1 */? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

unsigned int occupied? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

Method buckets[1]? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

};

Cache其實(shí)就是一個(gè)存儲Method的鏈表,主要是為了優(yōu)化方法調(diào)用的性能腮考。當(dāng)對象receiver調(diào)用方法message時(shí)雇毫,首先根據(jù)對象receiver的isa指針查找到它對應(yīng)的類,然后在類的methodLists中搜索方法踩蔚,如果沒有找到棚放,就使用super_class指針到父類中的methodLists查找,一旦找到就調(diào)用方法馅闽。如果沒有找到席吴,有可能消息轉(zhuǎn)發(fā),也可能忽略它捞蛋。但這樣查找方式效率太低孝冒,因?yàn)橥粋€(gè)類大概只有20%的方法經(jīng)常被調(diào)用,占總調(diào)用次數(shù)的80%拟杉。所以使用Cache來緩存經(jīng)常調(diào)用的方法庄涡,當(dāng)調(diào)用方法時(shí),優(yōu)先在Cache查找搬设,如果沒有找到穴店,再到methodLists查找

看了那么多元素概念,我們請出objc_msgSend在message.h中的定義:

/**

* Sends a message with a simple return value to an instance of a class.

*

* @param self A pointer to the instance of the class that is to receive the message.

* @param op The selector of the method that handles the message.

* @param ...

*? A variable argument list containing the arguments to the method.

*

* @return The return value of the method.

*

* @note When it encounters a method call, the compiler generates a call to one of the

*? functions \c objc_msgSend, \c objc_msgSend_stret, \c objc_msgSendSuper, or \c objc_msgSendSuper_stret.

*? Messages sent to an object’s superclass (using the \c super keyword) are sent using \c objc_msgSendSuper;

*? other messages are sent using \c objc_msgSend. Methods that have data structures as return values

*? are sent using \c objc_msgSendSuper_stret and \c objc_msgSend_stret.

*/

OBJC_EXPORT id objc_msgSend(id self, SEL op, ...)

OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0);

上面的注釋翻譯過來就是:

當(dāng)編譯器遇到一個(gè)方法調(diào)用時(shí)拿穴,它會將方法的調(diào)用翻譯成以下函數(shù)中的一個(gè) `objc_msgSend`泣洞、`objc_msgSend_stret`、`objc_msgSendSuper` 和 `objc_msgSendSuper_stret`默色。

發(fā)送給對象的父類的消息會使用 `objc_msgSendSuper`

有數(shù)據(jù)結(jié)構(gòu)作為返回值的方法會使用 `objc_msgSendSuper_stret` 或 `objc_msgSend_stret`

其它的消息都是使用 `objc_msgSend` 發(fā)送的

現(xiàn)在讓我們看一下objc_msgSend它具體是如何發(fā)送消息:

首先根據(jù)receiver對象的isa指針獲取它對應(yīng)的class球凰;

優(yōu)先在class的cache查找message方法,如果找不到腿宰,再到methodLists查找呕诉;

如果沒有在class找到,再到super_class查找吃度;

一旦找到message這個(gè)方法甩挫,就執(zhí)行它實(shí)現(xiàn)的IMP。


message

你以為objc_msgSend到這里就結(jié)束了椿每,顯然不是伊者,這只是針對有方法實(shí)現(xiàn)的plan a英遭,當(dāng)objc_msgSend找不到實(shí)現(xiàn)的IMP時(shí),程序并不會直接崩潰亦渗,而是給開發(fā)者一個(gè)plan b:方法決議與消息轉(zhuǎn)發(fā)贪绘。這是有一組NSObject里的方法實(shí)現(xiàn)的,開發(fā)者只需要重寫響應(yīng)的方法即可央碟。具體流程如圖:


方法決議與消息轉(zhuǎn)發(fā)


注意:第一步當(dāng)方法為實(shí)例方法時(shí)用:resolveInstanceMethod ,方法為類方法時(shí)用resolveClassMethod

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末均函,一起剝皮案震驚了整個(gè)濱河市亿虽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌苞也,老刑警劉巖洛勉,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異如迟,居然都是意外死亡收毫,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門殷勘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來此再,“玉大人,你說我怎么就攤上這事玲销∈淠矗” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵贤斜,是天一觀的道長策吠。 經(jīng)常有香客問我,道長瘩绒,這世上最難降的妖魔是什么猴抹? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮锁荔,結(jié)果婚禮上蟀给,老公的妹妹穿的比我還像新娘。我一直安慰自己阳堕,他們只是感情好坤溃,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嘱丢,像睡著了一般薪介。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上越驻,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天汁政,我揣著相機(jī)與錄音道偷,去河邊找鬼。 笑死记劈,一個(gè)胖子當(dāng)著我的面吹牛勺鸦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播目木,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼换途,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了刽射?” 一聲冷哼從身側(cè)響起军拟,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎誓禁,沒想到半個(gè)月后懈息,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡摹恰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年辫继,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片俗慈。...
    茶點(diǎn)故事閱讀 38,569評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡姑宽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出闺阱,到底是詐尸還是另有隱情低千,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布馏颂,位于F島的核電站示血,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏救拉。R本人自食惡果不足惜难审,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望亿絮。 院中可真熱鬧告喊,春花似錦、人聲如沸派昧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蒂萎。三九已至秆吵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間五慈,已是汗流浹背纳寂。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工主穗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人毙芜。 一個(gè)月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓忽媒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親腋粥。 傳聞我的和親對象是個(gè)殘疾皇子晦雨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評論 2 348

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,686評論 0 9
  • 轉(zhuǎn)載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 729評論 0 2
  • Objective-C語言是一門動態(tài)語言隘冲,他將很多靜態(tài)語言在編譯和鏈接時(shí)期做的事情放到了運(yùn)行時(shí)來處理闹瞧。這種動態(tài)語言...
    tigger丨閱讀 1,388評論 0 8
  • 原文出處:南峰子的技術(shù)博客 Objective-C語言是一門動態(tài)語言,它將很多靜態(tài)語言在編譯和鏈接時(shí)期做的事放到了...
    _燴面_閱讀 1,220評論 1 5
  • 前言 runtime其實(shí)在我們?nèi)粘i_發(fā)過程中很少使用到对嚼,尤其是像我現(xiàn)在比較初級的程序猿就更用不到了。但是去面試很多...
    WolfTin閱讀 616評論 0 2