編寫高質量OC程式碼52建議總結:11.理解objc_msgSend的作用(訊息機制)

viiimaus發表於2017-02-09

在物件上呼叫方法,術語叫做“傳遞訊息”,訊息有“名稱”和“選擇器(方法)”,可以接收引數,還可能有返回值。OC是C的超集,C語言使用靜態繫結,在編譯期間就能決定執行時做呼叫的函式。

#include <stdio.h>

void printHello() {
    printf("hello, world!\n");
}

void printGoodbye() {
    printf("Goodbye, world!\n");
}

void doTheThing(int type) {
    if (type == 0) {
        printHello();
    } else {
        printGoodbye();
    }
}

編譯器在編譯程式碼的時候就知道有printHello和printGoodbye兩個函式了,會直接生成呼叫這些函式的指令,函式地址實際上是硬編碼在指令之中。

#include <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();
}

如果程式碼變成這樣,就得使用“動態繫結”,因為所要呼叫的函式直到執行期才能確定,第一個例子中,if 和 else 中都有函式呼叫指令,第二個例子中只有一個函式呼叫指令,待呼叫的函式地址無法硬編碼在指令中,要在執行期讀取出來。

給物件傳送訊息可以這麼寫:

id returnValue = [someObject messageName:parameter];
someObject叫做“接受者”,messageName叫做“選擇器”,選擇器與引數合起來叫做“訊息”。編譯器將上述語句轉換為C語言函式呼叫 “objc_msgSend”

void objc_msgSend(id self, SEL cmd, ...)
這是個“引數個數可變的函式”,能接受兩個或兩個以上的引數。第一個引數代表接受者,第二個引數代表選擇器,後續引數是訊息中的引數編譯器會把剛才的例子轉換為如下函式。

id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter);
objc_msgSend 會在接受者所屬的類中搜尋其“方法列表”,如果找到與“選擇器”名稱相符的程式碼就實現,沒找到就延繼承體系向上查詢,最終找不到,就實現“訊息轉發”。

備註:每個類都有一塊快取“快速對映表”,如果稍後還向該類傳送相同的訊息,執行就快。


執行環境中一些“邊界情況”,需要其他函式處理
  1.objc_msgSend_stret:如果待傳送的訊息要返回結構體,交給這個函式。
  2.objc_msgSend_fpre:如果訊息返回的是浮點數,交給這個函式。
  3.objc_msgSendSuper:給超類發訊息,用這個函式。也有與objc_msgSend_fpre和objc_msgSend_fpre等效的處理超類訊息的方法。
 

OC物件的每個方法都可以看做簡單的C函式,原型如下:

<return_type> Class_selector(id self, SEL _cmd, ...)
每個類裡都會有一張表格,其中的指標都會指向這種函式,選擇器的名稱則是查表時用的“鍵”,objc_msgSend用這張表來尋找應該執行的方法並跳轉實現。
  

“尾呼叫優化”:如果函式的最後一項操作是呼叫另外一個函式,編譯器會生成調轉至另一個函式所用的指令碼。不會向呼叫堆疊中推入新的“棧幀”。
注意:只有當函式的最後一項操作僅僅是呼叫其他函式而不會將其返回值另做他用時


相關文章