objc_msgSend解析

方法的本質(zhì)

我們通過clang命令 clang -rewrite-objc main.m 將main文件編譯成main.cpp文件

//main函數(shù)中方法的調(diào)用
LGPerson *person = [LGPerson alloc];
[person sayHello];

//clang后的方法的調(diào)用
LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayHello"));

可以看出 方法的調(diào)用本質(zhì)是 objc_msgSend來發(fā)送消息疤估。我們還可以手動(dòng)調(diào)用來驗(yàn)證一下
導(dǎo)入頭文件<objc/message.h>

LGPerson *person = [LGPerson alloc];
//1.方法的調(diào)用
[person study];
//2.消息發(fā)送 ((void (*)(id, SEL))(void *)objc_msgSend)(p, sel_registerName("study"));

打印結(jié)果如下


image.png

可以看到打印結(jié)果是一致的框仔,這也證明了[person study]等價(jià)于objc_msgSend(person, sel_registerName("study"))。

執(zhí)行父類的方法

- (instancetype)init {
    if (self = [super init]) {
        NSLog(@"%@", [self class]);
        NSLog(@"%@", [super class]);
    }
    return self;
}

先來看一下這段代碼打印的是什么


image.png

打印的結(jié)果都是 LGTeacher锐膜,這是為什么呢溪王?
我們用clang命令編譯LGTeacher.m,得到如下編譯代碼

static instancetype _I_ LGTeacher_init(LGTeacher * self, SEL _cmd) {
    if (self = ((LGTeacher *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("LGTeacher"))}, sel_registerName("init"))) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_x6_75ftxbbd4t97nl_2t2sh7kww0000gn_T_ LGTeacher_8d6cff_mi_0, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class")));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_x6_75ftxbbd4t97nl_2t2sh7kww0000gn_T_ LGTeacher_8d6cff_mi_1, ((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("LGStudent"))}, sel_registerName("class")));
    }
    return self;
}

[self class]發(fā)送的消息是objc_msgSend,消息接收者是self,方法編號(hào)是class
[super class]本質(zhì)是objc_msgSendSuper, 消息接收者也是self, 方法編號(hào)class, 所以[super class]打印出來的一樣是self這個(gè)對象所指的類,也就是LGStudent腮鞍。

小結(jié)

  1. [super class]中的super只是一個(gè)編譯指示器,就是讓當(dāng)前對象獲取該方法莹菱,越過本類移国,直接從父類找。
  2. 消息的接受這還是本類self

方法快速查找

image.png

搜索objc_msgSend 在源碼中以 objc-msg-arm64為例道伟,看objc_msg_arm64中的源碼迹缀,可以看到都是匯編代碼使碾。這里我們可以思考一個(gè)問題,蘋果為什么objc_msgSend這部分代碼要使用匯編來編寫呢祝懂?答案很簡單--效率票摇。匯編的效率是比c/c++更快的,因?yàn)閰R編大多是直接對寄存器的讀寫砚蓬,相比較對內(nèi)存的操作更底層矢门,效率也更高。

//進(jìn)入objc_msgSend流程
ENTRY _objc_msgSend
//流程開始灰蛙,無需frame
UNWIND _objc_msgSend, NoFrame
//判斷p0(消息接受者)是否存在颅和,不存在則重新開始執(zhí)行objc_msgSend
cmp p0, #0 // nil check and tagged pointer check
//如果支持小對象類型。返回小對象或空
#if SUPPORT_TAGGED_POINTERS
//b是進(jìn)行跳轉(zhuǎn)缕允,b.le是小于判斷峡扩,也就是小于的時(shí)候LNilOrTagged
b.le LNilOrTagged //  (MSB tagged pointer looks negative)
#else
//等于,如果不支持小對象障本,就LReturnZero
b.eq LReturnZero
#endif
//通過p13取isa
ldr p13, [x0] // p13 = isa
//通過isa取class并保存到p16寄存器中
GetClassFromIsa_p16 p13, 1, x0 // p16 = class
//LGetIsaDone是一個(gè)入口
LGetIsaDone:
// calls imp or objc_msgSend_uncached
//進(jìn)入到緩存查找或者沒有緩存查找方法的流程
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
// nil check判空處理教届,直接退出
b.eq LReturnZero // nil check
GetTaggedClass
b LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret
END_ENTRY _objc_msgSend

objc_msgSend 開始到找類對像cache方法結(jié)束的流程。
首先判斷receiver是否存在驾霜,以及是否是taggedPointer類型的指針案训,如果不是我們?nèi)〕鰧ο蟮膇sa指針(x13寄存器中),通過isa指針找到類對象(x16寄存器)粪糙,然后通過CacheLookup强霎,在類對象的cache中查找是否有方法緩存,如果有就調(diào)用蓉冈,如果沒有走objc_msg_uncached分支城舞。

方法慢速查找(從方法列表中查找)

方法在cache內(nèi)找不到就是調(diào)用_objc_msgSend_uncached函數(shù),我們在_objc_msgSend_uncached函數(shù)內(nèi)部可以看到它會(huì)調(diào)用MethodTableLookup函數(shù)寞酿,MethodTableLookup函數(shù)會(huì)調(diào)用_lookUpImpOrForward家夺。_lookUpImpOrForward函數(shù)在匯編內(nèi)是看不到,那我們直接在源碼內(nèi)搜索lookUpImpOrForward伐弹。

image.png
image.png

lookUpImpOrForward函數(shù)內(nèi)部首先會(huì)判斷cache內(nèi)部有沒有方法拉馋,因?yàn)樵诙嗑€程環(huán)境下現(xiàn)在cache可能有該方法。


image.png

如果本類沒有的話會(huì)調(diào)用getMethodNoSuper_nolock函數(shù)惨好,然后依次調(diào)用search_method_list_inline煌茴、findMethodInSortedMethodList函數(shù)。我們可以看到findMethodInSortedMethodList是采用二分查找來獲取方法的日川。


image.png

image.png

image.png

image.png

當(dāng)我們在本類里面找到方法時(shí)蔓腐,它會(huì)進(jìn)行g(shù)oto done,然后會(huì)進(jìn)入log_and_fill_cache函數(shù)逗鸣,可以看到log_and_fill_cache函數(shù)內(nèi)部調(diào)用了cache.insert()方法合住,也就是加入緩存里面了绰精。需要注意的是哪個(gè)類調(diào)用就會(huì)加入哪個(gè)類的cache里面撒璧,也就是如果子類調(diào)用父類的方法透葛,之后該方法會(huì)緩存到子類的cache。


image.png

image.png

如果本類也沒有方法卿樱,通過curClass = curClass->getSuperclass()僚害,把curClass轉(zhuǎn)換成父類,然后找父類的cache繁调,cache如果沒有在通過二分查找找方法列表萨蚕。如果再?zèng)]有把curClass轉(zhuǎn)換成父類的父類依次查找。
image.png

總結(jié):
消息的慢速查找流程:

  • 調(diào)用lookUpImpOrForward
  • 看本類的cache里面有沒有該方法
  • 如果沒有看本類的methodList有沒有該方法(二分查找)
  • 如果沒有看父類的cache里面有沒有該方法
  • 如果沒有看父類的methodList有沒有該方法(二分查找)
  • 然后逐級(jí)向上查找
  • 當(dāng)父類是nil的時(shí)候也就是查找到NSObject的時(shí)候蹄胰,都沒有的話就會(huì)進(jìn)入消息轉(zhuǎn)發(fā)流程岳遥。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市裕寨,隨后出現(xiàn)的幾起案子浩蓉,更是在濱河造成了極大的恐慌,老刑警劉巖宾袜,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捻艳,死亡現(xiàn)場離奇詭異,居然都是意外死亡庆猫,警方通過查閱死者的電腦和手機(jī)认轨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來月培,“玉大人嘁字,你說我怎么就攤上這事∩夹螅” “怎么了拳锚?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長寻行。 經(jīng)常有香客問我霍掺,道長,這世上最難降的妖魔是什么拌蜘? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任杆烁,我火速辦了婚禮,結(jié)果婚禮上简卧,老公的妹妹穿的比我還像新娘兔魂。我一直安慰自己,他們只是感情好举娩,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布析校。 她就那樣靜靜地躺著构罗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪智玻。 梳的紋絲不亂的頭發(fā)上遂唧,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天,我揣著相機(jī)與錄音吊奢,去河邊找鬼盖彭。 笑死,一個(gè)胖子當(dāng)著我的面吹牛页滚,可吹牛的內(nèi)容都是我干的召边。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼裹驰,長吁一口氣:“原來是場噩夢啊……” “哼隧熙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起幻林,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬榮一對情侶失蹤贞盯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后滋将,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體邻悬,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年随闽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了父丰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡掘宪,死狀恐怖蛾扇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情魏滚,我是刑警寧澤镀首,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站鼠次,受9級(jí)特大地震影響更哄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜腥寇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一成翩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧赦役,春花似錦麻敌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赢赊。三九已至,卻和暖如春级历,著一層夾襖步出監(jiān)牢的瞬間释移,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來泰國打工鱼喉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留秀鞭,地道東北人趋观。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓扛禽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親皱坛。 傳聞我的和親對象是個(gè)殘疾皇子编曼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

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