高效編寫代碼的方法(九):了解objc_msgSend

在OC中我們調(diào)用方法也叫作給對象發(fā)消息收班,消息包含了名字,選擇器顶瞳,參數(shù)及返回值等信息玖姑。

C中

一個C語言的例子:

#import <stdio.h> 
 
void printHello() {  
    printf("Hello, world!\n");  
}  
void printGoodbye() {  
    printf("Goodbye, world!\n");  
}  
 
void doTheThing(int type) {  
    if (type == 0) {  
        printHello();  
    } else {  
        printGoodbye();  
    }  
    return 0;  
} 

在C語言中愕秫,在不考慮使用內(nèi)聯(lián)函數(shù)的情況下,printHello和printGoodbye函數(shù)都是已知的焰络。在調(diào)用時戴甩,編譯器直接發(fā)出指令去進行調(diào)用,函數(shù)的地址通過硬解碼得到闪彼。

現(xiàn)在換一種寫法:

#import <stdio.h> 
 
void printHello() {  
    printf("Hello, world!\n");  
}  
void printGoodbye() {  
    printf("Goodbye, world!\n");  
}  
 
void doTheThing(int type) {  
    void (*fnc)();  
    if (type == 0) {  
        fnc = printHello;  
    } else {  
        fnc = printGoodbye;  
    }  
    fnc();  
    return 0;  
} 

以上代碼則使用了動態(tài)綁定的方法甜孤,直到運行的時候,fnc函數(shù)具體是什么函數(shù)是未知的畏腕。與第一段代碼不同的是缴川,這里獲得函數(shù)地址的方法不能硬解碼獲得,而是在運行期間得到描馅。

OC中

OC中把夸,對象調(diào)用方法,也叫給對象發(fā)送消息铭污,實際上是使用了動態(tài)綁定機制恋日。在底層膀篮,所有方法都是普通的C語言函數(shù),然而對象收到消息之后岂膳,究竟該調(diào)用哪個方法則完全于運行期決定誓竿,甚至可以在程序運行時改變,這些特性使得Objective-C成為一門真正的動態(tài)語言谈截。
通常我們在OC中這樣發(fā)送消息:

id returnValue = [someObject messageName:parameter];

someObject是消息的接受者,messageName是一個選擇器筷屡,parameter則為參數(shù)。選擇器+參數(shù) 就是我們所稱為的消息傻盟。
在底層速蕊,編譯器將我們的消息轉(zhuǎn)換文標準的C函數(shù)形式,如下:

void objc_msgSend(id self,SEL cmd,…)

self 為消息接收者娘赴,cmd為選擇器规哲,省略號為參數(shù),表示可變長度參數(shù)诽表。
因此唉锌,以上的消息轉(zhuǎn)換為標準的C函數(shù)后如下:

id returnValue = objc_msgSend(someObject,@selector(messageName),paramter)

之所以objc_msgSend方法總能找到正確的函數(shù)去執(zhí)行,原因如下:
其實每個類中都有一張方法列表去存儲這個類中有的方法竿奏,當發(fā)出objc_msgSend
方法時候袄简,就會順著列表去找這個方法是否存在,如果不存在泛啸,則向該類的父類繼續(xù)查找绿语,直到找到位置。如果始終沒有找到方法候址,那么就會進入到消息轉(zhuǎn)發(fā)機制(后續(xù)知識吕粹,以后章節(jié)會介紹) 。
OC runtime還有一個機制在于方法緩存岗仑,每調(diào)用完這個方法后匹耕,一個方法映射就會被緩存起來,如果之后調(diào)用相同的方法荠雕,那么就能直接從映射表里確定方法的位置稳其,而不用每次都需要查找,這樣執(zhí)行速度會快一點炸卑。

幾個特殊方法

objc_msgSend_stret
如果待發(fā)送的消息要返回結(jié)構(gòu)體既鞠,那么可交由此函數(shù)處理。只有當CPU的寄存器能夠容納得下消息返回類型時盖文,這個函數(shù)才能處理此消息嘱蛋。若是返回值無法容納于CPU寄存器中(比如說返回的結(jié)構(gòu)體太大了),那么就由另一個函數(shù)執(zhí)行派發(fā)。此時浑槽,那個函數(shù)會通過分配在棧上的某個變量來處理消息所返回的結(jié)構(gòu)體蒋失。

objc_msgSend_fpret
如果消息返回的是浮點數(shù),那么可交由此函數(shù)處理桐玻。在某些架構(gòu)的CPU中調(diào)用函數(shù)時篙挽,需要對“浮點數(shù)寄存器”(floating-point register)做特殊處理,也就是說镊靴,通常所用的objc_msgSend在這種情況下并不合適铣卡。這個函數(shù)是為了處理x86等架構(gòu)CPU中某些令人稍覺驚訝的奇怪狀況。

objc_msgSendSuper
如果要給超類發(fā)消息偏竟,例如[super message:parameter]煮落,那么就交由此函數(shù)處理。也有另外兩個與objc_msgSend_stret和objc_msgSend_fpret等效的函數(shù)踊谋,用于處理發(fā)給super的相應(yīng)消息蝉仇。

以上內(nèi)容摘抄自網(wǎng)上翻譯,因為英文原文這部分實在是不太好理解殖蚕。
我覺得可以簡單的按照字面意思來進行選擇轿衔,比如你希望函數(shù)返回體為結(jié)構(gòu)體,那么就使用objc_msgSend_stret睦疫,否則有幾率會崩潰害驹。返回值為浮點數(shù)時也是相同道理。

上文說過蛤育,當找到相應(yīng)的方法時宛官,會跳轉(zhuǎn)過去。之所以可以這樣實現(xiàn)瓦糕,是因為每一個Objective-C函數(shù)都可以看作是一個簡單的C函數(shù)底洗,原型如下:

<return_type> Class_selector(id self,SEL _cmd,...)

以上Class及selector的命名是為了方便理解。每個類中都有一張類似于字典的方法表刻坊,而selector就相當于查找方法的key枷恕,objc_msgSend函數(shù)就是通過查這張表來實現(xiàn)跳轉(zhuǎn)的党晋。之所以以上原型和objc_msgSend方法長的非常相像谭胚,是為了更好使用tail-call技術(shù)來時方法的跳轉(zhuǎn)更加優(yōu)化。

如果某函數(shù)的最后一項操作是調(diào)用另外一個函數(shù)未玻,那么就可以運用“tail-call”技術(shù)灾而。
此時編譯器會生成調(diào)轉(zhuǎn)至另一函數(shù)所需的指令碼,而不會向調(diào)用堆棧中推入新的“棧幀”扳剿。tail-call使用的條件比較苛刻旁趟,除了要求函數(shù)的最后一項操作是調(diào)用另外一個函數(shù)外,庇绽,并且要求另外一個函數(shù)不是有返回值的函數(shù)類型锡搜。tail-call對objc_msgSend非常關(guān)鍵橙困,如果不這么做的話,那么每次調(diào)用Objective-C方法之前耕餐,都需要為調(diào)用objc_msgSend函數(shù)準備“棧幀”凡傅,若是不優(yōu)化,還會過早地發(fā)生“棧溢出”(stack overflow)現(xiàn)象肠缔。

在寫OC中夏跷,我們其實并不需要了解那么多底層的東西,但是我們需要知道調(diào)用一個方法之后明未,OC底層都發(fā)生了什么槽华。

總結(jié)

  • 1 一個消息包含接受者,選擇子和參數(shù)趟妥。調(diào)用一個方法相當于像對象發(fā)送一條消息猫态。
  • 2 當發(fā)送消息是,動態(tài)綁定機制會幫助我們查找方法的實現(xiàn)并進行運行披摄。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末懂鸵,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子行疏,更是在濱河造成了極大的恐慌匆光,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件酿联,死亡現(xiàn)場離奇詭異终息,居然都是意外死亡,警方通過查閱死者的電腦和手機贞让,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門周崭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人喳张,你說我怎么就攤上這事续镇。” “怎么了销部?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵摸航,是天一觀的道長。 經(jīng)常有香客問我舅桩,道長酱虎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任擂涛,我火速辦了婚禮读串,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己恢暖,他們只是感情好排监,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著杰捂,像睡著了一般社露。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上琼娘,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天峭弟,我揣著相機與錄音,去河邊找鬼脱拼。 笑死瞒瘸,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的熄浓。 我是一名探鬼主播情臭,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼赌蔑!你這毒婦竟也來了俯在?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤娃惯,失蹤者是張志新(化名)和其女友劉穎跷乐,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體趾浅,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡愕提,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了皿哨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片浅侨。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖证膨,靈堂內(nèi)的尸體忽然破棺而出如输,到底是詐尸還是另有隱情,我是刑警寧澤央勒,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布不见,位于F島的核電站,受9級特大地震影響订歪,放射性物質(zhì)發(fā)生泄漏脖祈。R本人自食惡果不足惜肆捕,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一刷晋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦眼虱、人聲如沸喻奥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽撞蚕。三九已至,卻和暖如春过牙,著一層夾襖步出監(jiān)牢的瞬間甥厦,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工寇钉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留刀疙,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓扫倡,卻偏偏與公主長得像谦秧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子撵溃,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

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

  • 前言 runtime其實在我們?nèi)粘i_發(fā)過程中很少使用到疚鲤,尤其是像我現(xiàn)在比較初級的程序猿就更用不到了。但是去面試很多...
    WolfTin閱讀 611評論 0 2
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉缘挑,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,679評論 0 9
  • 本文詳細整理了 Cocoa 的 Runtime 系統(tǒng)的知識集歇,它使得 Objective-C 如虎添翼,具備了靈活的...
    lylaut閱讀 792評論 0 4
  • 兜兜轉(zhuǎn)轉(zhuǎn)兜兜语淘,我又回到了熱電鬼悠。大學(xué)學(xué)熱能,當時迷茫的很亏娜,沒有好好學(xué)功課焕窝,研究生轉(zhuǎn)專業(yè)到光電,覺得自己大概不用再搞熱...
    于淥閱讀 365評論 0 0
  • JS 如何清除頁面緩存 1.動態(tài)頁面:index.asp?id=.... 2.使用jquery,$.ajaxSet...
    浮生小孟閱讀 294評論 0 2