iOS 理解objc_msgSend的作用

好記性不如爛筆頭,這塊偏硬的概念還是自己打一遍理解下好

在對象上調(diào)用方法是Objective-C中經(jīng)常使用的功能绅这。用Objective-C的術(shù)語來說,這叫做“傳遞消息”(pass a message)在辆。消息有“名稱”(name)或“選擇子”(selector)证薇,可以接受參數(shù),而且可能還有返回值匆篓。
由于Objective-C是C的超集浑度,所以最好先理解C語言的函數(shù)調(diào)用方式。C語言使用“靜態(tài)綁定”(static binding)鸦概,也就是說箩张,在編譯期就能決定運(yùn)行時(shí)所應(yīng)調(diào)用的函數(shù)。以下列代碼為例:

#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;  
} 

如果不考慮“內(nèi)聯(lián)”(inline)窗市,那么編譯器在編譯代碼的時(shí)候就已經(jīng)知道程序中有printHello與printGoodbye這兩個(gè)函數(shù)了先慷,于是會直接生成調(diào)用這些函數(shù)的指令。而函數(shù)地址實(shí)際上是硬編碼在指令之中的咨察。若是將剛才那段代碼寫成下面這樣论熙,會如何呢?

#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;  
} 
這

時(shí)就得使用“動態(tài)綁定”(dynamic binding)了摄狱,因?yàn)樗{(diào)用的函數(shù)直到運(yùn)行期才能確定脓诡。編譯器在這種情況下生成的指令與剛才那個(gè)例子不同,在第一個(gè)例子中媒役,if與else語句里都有函數(shù)調(diào)用指令祝谚。而在第二個(gè)例子中,只有一個(gè)函數(shù)調(diào)用指令刊愚,不過待調(diào)用的函數(shù)地址無法硬編碼在指令之中踊跟,而是要在運(yùn)行期讀取出來。
在Objective-C中,如果向某對象傳遞消息商玫,那就會使用動態(tài)綁定機(jī)制來決定需要調(diào)用的方法箕憾。在底層,所有方法都是普通的C語言函數(shù)拳昌,然而對象收到消息之后袭异,究竟該調(diào)用哪個(gè)方法則完全于運(yùn)行期決定,甚至可以在程序運(yùn)行時(shí)改變炬藤,這些特性使得Objective-C成為一門真正的動態(tài)語言御铃。
給對象發(fā)送消息可以這樣來寫:
id returnValue = [someObject messageName:parameter];
在本例中,someObject叫做“接收者”(receiver)沈矿,messageName叫做“選擇子”(selector)上真。選擇子與參數(shù)合起來稱為“消息”(message)。編譯器看到此消息后羹膳,將其轉(zhuǎn)換為一條標(biāo)準(zhǔn)的C語言函數(shù)調(diào)用睡互,所調(diào)用的函數(shù)乃是消息傳遞機(jī)制中的核心函數(shù),叫做objc_msgSend陵像,其“原型”(prototype)如下:
void objc_msgSend(id self, SEL cmd, ...)
這是個(gè)“參數(shù)個(gè)數(shù)可變的函數(shù)”(variadic function)就珠,能接受兩個(gè)或兩個(gè)以上的參數(shù)。第一個(gè)參數(shù)代表接收者醒颖,第二個(gè)參數(shù)代表選擇子(SEL是選擇子的類型)妻怎,后續(xù)參數(shù)就是消息中的那些參數(shù),其順序不變泞歉。選擇子指的就是方法的名字逼侦。“選擇子”與“方法”這兩個(gè)詞經(jīng)常交替使用疏日。編譯器會把剛才那個(gè)例子中的消息轉(zhuǎn)換為如下函數(shù):
id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter);
objc_msgSend函數(shù)會依據(jù)接收者與選擇子的類型來調(diào)用適當(dāng)?shù)姆椒ǔソ唷榱送瓿纱瞬僮魅龊海摲椒ㄐ枰诮邮照咚鶎俚念愔兴褜て洹胺椒斜怼保╨ist of methods)沟优,如果能找到與選擇子名稱相符的方法,就跳至其實(shí)現(xiàn)代碼睬辐。若是找不到挠阁,那就沿著繼承體系繼續(xù)向上查找,等找到合適的方法之后再跳轉(zhuǎn)溯饵。如果最終還是找不到相符的方法侵俗,那就執(zhí)行“消息轉(zhuǎn)發(fā)”(message forwarding)操作。消息轉(zhuǎn)發(fā)將在第12條中詳解丰刊。
這么說來隘谣,想調(diào)用一個(gè)方法似乎需要很多步驟。所幸objc_msgSend會將匹配結(jié)果緩存在“快速映射表”(fast map)里面,每個(gè)類都有這樣一塊緩存寻歧,若是稍后還向該類發(fā)送與選擇子相同的消息掌栅,那么執(zhí)行起來就很快了。當(dāng)然啦码泛,這種“快速執(zhí)行路徑”(fast path)還是不如“靜態(tài)綁定的函數(shù)調(diào)用操作”(statically bound function call)那樣迅速猾封,不過只要把選擇子緩存起來了,也就不會慢很多噪珊,實(shí)際上晌缘,消息派發(fā)(message dispatch)并非應(yīng)用程序的瓶頸所在。假如真是個(gè)瓶頸的話痢站,那你可以只編寫純C函數(shù)磷箕,在調(diào)用時(shí)根據(jù)需要,把Objective-C對象的狀態(tài)傳進(jìn)去阵难。
前面講的這部分內(nèi)容只描述了部分消息的調(diào)用過程搀捷,其他“邊界情況”(edge case)則需要交由Objective-C運(yùn)行環(huán)境中的另一些函數(shù)來處理:
objc_msgSend_stret。如果待發(fā)送的消息要返回結(jié)構(gòu)體多望,那么可交由此函數(shù)處理嫩舟。只有當(dāng)CPU的寄存器能夠容納得下消息返回類型時(shí),這個(gè)函數(shù)才能處理此消息怀偷。若是返回值無法容納于CPU寄存器中(比如說返回的結(jié)構(gòu)體太大了)家厌,那么就由另一個(gè)函數(shù)執(zhí)行派發(fā)。此時(shí)椎工,那個(gè)函數(shù)會通過分配在棧上的某個(gè)變量來處理消息所返回的結(jié)構(gòu)體饭于。
objc_msgSend_fpret。如果消息返回的是浮點(diǎn)數(shù)维蒙,那么可交由此函數(shù)處理掰吕。在某些架構(gòu)的CPU中調(diào)用函數(shù)時(shí),需要對“浮點(diǎn)數(shù)寄存器”(floating-point register)做特殊處理颅痊,也就是說殖熟,通常所用的objc_msgSend在這種情況下并不合適。這個(gè)函數(shù)是為了處理x86等架構(gòu)CPU中某些令人稍覺驚訝的奇怪狀況斑响。
objc_msgSendSuper菱属。如果要給超類發(fā)消息,例如[super message:parameter]舰罚,那么就交由此函數(shù)處理纽门。也有另外兩個(gè)與objc_msgSend_stret和objc_msgSend_fpret等效的函數(shù),用于處理發(fā)給super的相應(yīng)消息营罢。
剛才曾提到赏陵,objc_msgSend等函數(shù)一旦找到應(yīng)該調(diào)用的方法實(shí)現(xiàn)之后,就會“跳轉(zhuǎn)過去”。之所以能這樣做蝙搔,是因?yàn)镺bjective-C對象的每個(gè)方法都可以視為簡單的C函數(shù)候醒,其原型如下:
<return_type> Class_selector(id self, SEL _cmd, ...)
真正的函數(shù)名和上面寫的可能不太一樣,筆者用“類”(class)和“選擇子”(selector)來命名是想解釋其工作原理杂瘸。每個(gè)類里都有一張表格倒淫,其中的指針都會指向這種函數(shù),而選擇子的名稱則是查表時(shí)所用的“鍵”败玉。objc_msgSend等函數(shù)正是通過這張表格來尋找應(yīng)該執(zhí)行的方法并跳至其實(shí)現(xiàn)的敌土。請注意,原型的樣子和objc_msgSend函數(shù)很像运翼。這不是巧合返干,而是為了利用“尾調(diào)用優(yōu)化”(tail-call optimization)技術(shù),令“跳至方法實(shí)現(xiàn)”這一操作變得更簡單些血淌。
如果某函數(shù)的最后一項(xiàng)操作是調(diào)用另外一個(gè)函數(shù)矩欠,那么就可以運(yùn)用“尾調(diào)用優(yōu)化”技術(shù)。編譯器會生成調(diào)轉(zhuǎn)至另一函數(shù)所需的指令碼悠夯,而且不會向調(diào)用堆棧中推入新的“棧幀”(frame stack)癌淮。只有當(dāng)某函數(shù)的最后一個(gè)操作僅僅是調(diào)用其他函數(shù)而不會將其返回值另作他用時(shí),才能執(zhí)行“尾調(diào)用優(yōu)化”沦补。這項(xiàng)優(yōu)化對objc_msgSend非常關(guān)鍵乳蓄,如果不這么做的話,那么每次調(diào)用Objective-C方法之前夕膀,都需要為調(diào)用objc_msgSend函數(shù)準(zhǔn)備“棧幀”虚倒,大家在“棧蹤跡”(stack trace)中可以看到這種“棧幀”。此外产舞,若是不優(yōu)化魂奥,還會過早地發(fā)生“棧溢出”(stack overflow)現(xiàn)象。
在實(shí)際編寫Objective-C代碼的過程中易猫,大家無須擔(dān)心這一問題耻煤,不過應(yīng)該了解其底層工作原理。這樣的話擦囊,你就會明白违霞,在發(fā)送消息時(shí),代碼究竟是如何執(zhí)行的瞬场,而且也能理解,為何在調(diào)試的時(shí)候涧郊,椆岜唬“回溯”(backtrace)信息中總是出現(xiàn)objc_msgSend。

要點(diǎn)

  • 消息由接收者、選擇子及參數(shù)構(gòu)成彤灶。給某對象“發(fā)送消息”(invoke a message)也就相當(dāng)于在該對象上“調(diào)用方法”(call a method)看幼。
  • 發(fā)給某對象的全部消息都要由“動態(tài)消息派發(fā)系統(tǒng)”(dynamic message dispatch system)來處理,該系統(tǒng)會查出對應(yīng)的方法幌陕,并執(zhí)行其代碼诵姜。

讀Effective Objective-C 2.0 有感

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市搏熄,隨后出現(xiàn)的幾起案子棚唆,更是在濱河造成了極大的恐慌,老刑警劉巖心例,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宵凌,死亡現(xiàn)場離奇詭異,居然都是意外死亡止后,警方通過查閱死者的電腦和手機(jī)瞎惫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來译株,“玉大人瓜喇,你說我怎么就攤上這事∏该樱” “怎么了欠橘?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長现恼。 經(jīng)常有香客問我肃续,道長,這世上最難降的妖魔是什么叉袍? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任始锚,我火速辦了婚禮,結(jié)果婚禮上喳逛,老公的妹妹穿的比我還像新娘瞧捌。我一直安慰自己,他們只是感情好润文,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布姐呐。 她就那樣靜靜地躺著,像睡著了一般典蝌。 火紅的嫁衣襯著肌膚如雪曙砂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天骏掀,我揣著相機(jī)與錄音鸠澈,去河邊找鬼柱告。 笑死,一個(gè)胖子當(dāng)著我的面吹牛笑陈,可吹牛的內(nèi)容都是我干的际度。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼涵妥,長吁一口氣:“原來是場噩夢啊……” “哼乖菱!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蓬网,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤窒所,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后拳缠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體墩新,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年窟坐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了海渊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,731評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡哲鸳,死狀恐怖臣疑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情徙菠,我是刑警寧澤讯沈,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站婿奔,受9級特大地震影響缺狠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜萍摊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一挤茄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧冰木,春花似錦穷劈、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至逼龟,卻和暖如春评凝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背审轮。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工肥哎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留辽俗,地道東北人疾渣。 一個(gè)月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓篡诽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親榴捡。 傳聞我的和親對象是個(gè)殘疾皇子杈女,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評論 2 354

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