OC-Runtime消息發(fā)送

Objective-C動態(tài)性的根源在方法的調用是通過message來實現(xiàn)的勋又,一次發(fā)送message的過程就是一次方法的調用過程苦掘。發(fā)送message只需要指定對象和SELRuntimeobjc_msgSend會根據(jù)在信息在對象isa指針指向的Class中尋找該SEL對應的IMP楔壤,從而完成方法的調用鹤啡。

消息發(fā)送流程

一張圖描述下對象的內存布局


image

實例對象中存放 isa 指針以及實例變量,有 isa 指針可以找到實例對象所屬的類對象 (類也是對象蹲嚣,面向對象中一切都是對象)揉忘,類中存放著實例方法列表,在這個方法列表中 SEL 作為 key端铛,IMP 作為 value。 在編譯時期疲眷,根據(jù)方法名字會生成一個唯一的 Int 標識禾蚕,這個標識就是 SELIMP 其實就是函數(shù)指針 指向了最終的函數(shù)實現(xiàn)狂丝。整個 Runtime 的核心就是 objc_msgSend 函數(shù)换淆,通過給類發(fā)送 SEL 以傳遞消息,找到匹配的 IMP 再獲取最終的實現(xiàn)

類中的 super_class 指針可以追溯整個繼承鏈几颜。向一個對象發(fā)送消息時倍试,Runtime 會根據(jù)實例對象的 isa 指針找到其所屬的類,并自底向上直至根類(NSObject)中 去尋找SEL 所對應的方法蛋哭,找到后就運行整個方法县习。

metaClass是元類,也有 isa 指針、super_class 指針躁愿。其中保存了類方法列表叛本。

如下是 objc/runtime.h 中定義的類的結構:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#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; // 遵循的協(xié)議列表
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

SEL 與 IMP

SEL 可以將其理解為方法的 ID. 結構如下:

typedef struct objc_selector *SEL;

struct objc_selector {
    char *name;                       OBJC2_UNAVAILABLE;
    char *types;                      OBJC2_UNAVAILABLE;
};

IMP 可以理解為函數(shù)指針来候,指向了最終的實現(xiàn)。

SEL 與 IMP 的關系非常類似于 HashTable 中 key 與 value 的關系逸雹。OC 中不支持函數(shù)重載的原因就是因為一個類的方法列表中不能存在兩個相同的 SEL 营搅。但是多個方法卻可以在不同的類中有一個相同的 SEL,不同類的實例對象執(zhí)行相同的 SEL 時梆砸,會在各自的方法列表中去根據(jù) SEL 去尋找自己對應的IMP转质。這使得OC可以支持函數(shù)重寫。

消息傳遞機制

當一個對象 sender 調用代碼時辫樱,實際上是調用了runtime的objc_msgSend函數(shù)峭拘,所以OC的方法調用并不像C函數(shù)一樣能按照地址直接取用,而是經過了一系列的過程狮暑。

下面將通過代碼驗證下鸡挠,新建一個工程,創(chuàng)建一個繼承于NSObject的子類(RuntimeTest)搬男,.m文件中寫入一下代碼(驗證 - (void)changeVaule:(NSString *)vauleString如何調用拣展,工具Xcode10)

@implementation RuntimeTest
- (void)test:(NSString *)vaule {
    [self changeVaule:vaule];
}

- (void)changeVaule:(NSString *)vauleString {
    
}
@end

開啟終端,進入工程存放文件目錄缔逛,使用clang將RuntimeTest.m文件轉換成RuntimeTest.cpp文件查看其C函數(shù)調用實現(xiàn)

clang -rewrite-objc RuntimeTest.m

然后從RuntimeTest.cpp轉換后實現(xiàn)备埃,下面??是截取部分類容:

static void _I_RuntimeTest_test_(RuntimeTest * self, SEL _cmd, NSString * _Nonnull vaule) {
    ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)self, sel_registerName("changeVaule:"), (NSString * _Nonnull)vaule);
}


static void _I_RuntimeTest_changeVaule_(RuntimeTest * self, SEL _cmd, NSString *vauleString) {

}

去掉強制轉換,可以看出最終調用方法是:

objc_msgSend(self, sel_registerName("changeVaule:"), vaule);

其中:

  1. sel_registerName("changeVaule:")是一個SEL類型的值褐奴,用于標示類中的一個方法(類比C的函數(shù)指針來理解)
  2. sel_registerName(methodName)表達式用于獲得當前類中methodName方法的對應SEL

obj_msgSend(recevier, selector, ...)函數(shù)的主要執(zhí)行流程大致是:

根據(jù)SEL,首先在Class中的緩存查找imp(沒緩存則初始化緩存)按脚,如果沒找到,則向父類的Class查找敦冬。如果一直查找到根類仍舊沒有實現(xiàn)辅搬,則用_objc_msgForward函數(shù)指針代替imp。最后脖旱,執(zhí)行這個imp堪遂。

_objc_msgForward是用于消息轉發(fā)的。這個函數(shù)的實現(xiàn)并沒有在objc-runtime的開源代碼里面萌庆,而是在Foundation框架里面實現(xiàn)的溶褪。加上斷點啟動程序后,會發(fā)現(xiàn)__CFInitialize這個方法會調用objc_setForwardHandler函數(shù)來注冊一個實現(xiàn)践险。

OC-Runtime消息轉發(fā):OC-Runtime消息轉發(fā)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末猿妈,一起剝皮案震驚了整個濱河市吹菱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌于游,老刑警劉巖毁葱,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異贰剥,居然都是意外死亡倾剿,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門蚌成,熙熙樓的掌柜王于貴愁眉苦臉地迎上來前痘,“玉大人,你說我怎么就攤上這事担忧∏鄣蓿” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵瓶盛,是天一觀的道長最欠。 經常有香客問我,道長惩猫,這世上最難降的妖魔是什么芝硬? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮轧房,結果婚禮上拌阴,老公的妹妹穿的比我還像新娘。我一直安慰自己奶镶,他們只是感情好迟赃,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著厂镇,像睡著了一般纤壁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上捺信,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天酌媒,我揣著相機與錄音,去河邊找鬼残黑。 笑死,一個胖子當著我的面吹牛斋否,可吹牛的內容都是我干的梨水。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼茵臭,長吁一口氣:“原來是場噩夢啊……” “哼疫诽!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤奇徒,失蹤者是張志新(化名)和其女友劉穎雏亚,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體摩钙,經...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡罢低,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了胖笛。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片网持。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖长踊,靈堂內的尸體忽然破棺而出功舀,到底是詐尸還是另有隱情,我是刑警寧澤身弊,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布辟汰,位于F島的核電站,受9級特大地震影響阱佛,放射性物質發(fā)生泄漏帖汞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一瘫絮、第九天 我趴在偏房一處隱蔽的房頂上張望涨冀。 院中可真熱鬧,春花似錦麦萤、人聲如沸鹿鳖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽翅帜。三九已至,卻和暖如春命满,著一層夾襖步出監(jiān)牢的瞬間涝滴,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工胶台, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留歼疮,地道東北人。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓诈唬,卻偏偏與公主長得像韩脏,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子铸磅,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354