在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)并進行運行披摄。