Objective-c方法調用本質上是消息傳遞。消息包括消息名稱name
,選擇器selector
(其實就是函數指針)。傳遞的消息能夠接受參數,也可能有返回值。緩存
要理解OC的消息傳遞,就該說一下C語言的函數調用方式,畢竟OC是C語言的延伸語言。C語言使用靜態綁定Static binding
進行函數調用,說人話就是C語言在編譯期就能決定運行時要調用的函數。好比:架構
#import <stdio.h>
void printHello() {
printf("Hello,world!\n");
}
void printGoodbye() {
printf("Goodbye,world!\n");
}
void doSomeThing(int type) {
if (type == 0) {
printHello();
}else {
printGoodbye();
}
return 0;
}
複製代碼
若是不考慮內聯函數的狀況(C語言中爲提升函數調用的效率,直接將函數體對函數調用進行替換的方式),那麼在C語言中,編譯器在編譯期就已經知道程序中有printHello和printGoodbye這兩個函數了,因而就能夠直接調用這兩個函數的指令,實際上這兩個函數的地址是被硬編碼在指令裏面的。函數
咱們把上述例子改變一下:性能
#import <stdio.h>
void printHello() {
printf("Hello,world!\n");
}
void printGoodbye() {
printf("Goodbye,world!\n");
}
void doSomeThing(int type) {
void (*fnc)();
if (type == 0) {
fnc = printHello;
}else {
fnc = printGoodbye;
}
fnc();
return 0;
}
複製代碼
這時候就使用了動態綁定Dynamic binding
,所調用的函數要到運行期才能肯定。在第二個例子的代碼中,只有一個函數調用指令,而且待調用函數的函數地址並不能硬編碼在指令中,而是要等到運行期才能讀取。優化
在OC中,傳遞消息其實就是用動態綁定機制來決定須要調用的方法。在OC底層,全部的方法都是普通的C語言函數,對象收到消息後,調用哪一個方法徹底由運行時決定,甚至在程序運行的時候發生改變,因此OC是一門動態語言。ui
一個典型的方法調用或者說給對象發送消息能夠這樣來寫:編碼
id returnValue = [someObj messageName:parameter];
複製代碼
someObj
叫作接收者
,messageName
是選擇器
,選擇器及其參數,一塊兒被稱爲消息
(Message)。編譯器看到這條消息後,會把它轉化爲一條標準的C語言函數調用,這個是整個OC運行時消息傳遞機制的核心,也就是objc_msgSend
,原型以下:spa
void objc_msgSend(id self,SEL cmd,...) 複製代碼
objc_msgSend
是一個參數可變函數,能接受兩個或兩個以上參數。第一個參數是接受者,第二個參數是選擇器,其中SEL
爲選擇器的類型,後面就是消息中的參數了,順序不變。因此把剛纔那個典型的方法調用轉換以下:指針
id returnValue = objc_msgSend(someObj,
@selector(messageName:),
parameter);
複製代碼
objc_msgSend
函數會依據接受者和選擇器的類型調用適當的方法。此時,objc_msgSend
會在接收者所屬的類中搜尋一個方法列表,若是能找到和選擇器名稱相符合的方法,就跳轉到其實現的代碼。若是找不到,就沿着繼承體系繼續向上查找,等找到合適的方法後再跳轉。若是這時候仍是找不到符合的方法,就執行消息轉發(message forwarding)操做,這個會在下一篇文章中詳解。下面用一個流程圖說明上述過程:code
在整個過程當中,objc_msgSend會將匹配結果緩存在一個快速映射表
裏,每個類都有一塊這樣的緩存。以後同一個類發送相同選擇器的消息時,執行起來就會更快。但實際上,這種方式的速度仍是不如靜態綁定
那麼快,但大多數狀況下,消息發送並非一個應用程序的性能瓶頸,若是真的出現了性能問題,能夠編寫單純的C語言函數,在調用時根據須要,傳入OC對象便可。
一些邊界狀況,OC的運行環境會提供另一些對應的函數處理:
objc_msgSend_stret
:處理待發送消息須要返回結構體的狀況,但當結構體過大,致使CPU寄存器沒法容納時,會把消息交給另外一個函數派發,把返回的機構體經過分配在棧上的某個變量處理。objc_msgSend_fpret
:處理待發送消息須要返回浮點數的狀況。在某些架構的CPU中,須要對浮點寄存器作一些特殊處理,因此這時候調用objc_msgSend
並不合適。objc_msgSendSuper
:向父類發送消息。objc_msgSend
找到對應的方法調用實現後,會直接跳轉過去,OC中每個方法均可以理解爲是一個簡單的C語言函數,原型以下:
<return_type> Class_selecotor(id self,SEL _cmd,...)
複製代碼
每一個類的方法列表中的指針都會指向這種函數,選擇器就是查表時候所用的「鍵」。
這裏咱們發現,方法的原型模樣和objc_msgSend
有些相似,這不是巧合,而是OC利用了尾調用優化。
若是某個函數最後一項操做也是調用另外一個函數,那麼就能夠利用尾調用優化了。編譯器會生成跳轉到另外一個函數所須要的指令碼,並且不會向調用棧中推入新的棧幀。這裏有幾個須要解釋的地方:
objc_msgSend
準備棧幀
,是一個進棧的過程。好比最後返回
return [self message:someMsg];
時能夠進行尾調用優化,但return [self message:someMsg]+1;
這種狀況不行,由於雖然調用了函數,但最終調用的函數是爲返回值作準備的。