Messaging
這一章講述消息怎樣被轉換為 objc_msgSend 方法的調用,如何通過名稱引用方法,如何利用 objc_msgSend
,如何避免動態綁定。
objc_msgSend 方法
在 Objective-C
中,知道運行時,消息才綁定到方法的實現上。編譯器把一個消息表達式
[receiver message]
轉換成一個調用消息方法 objc_msgSend這個函數包含兩個重要的參數,接受者和在消息中出現的方法名(方法選擇器)。
objc_msgSend(receiver, selector)
任何在消息中傳遞的參數都會傳遞給 objc_msgSend
:
objc_msgSend(receiver, selector, arg1, arg2, ...)
消息方法做了一切必要的動態綁定工作。
- 首選找到選擇器指向的程序(方法實現),同樣的函數,每一個類的實現也是不一樣的。通過接受者,可以準確的找到方法的實現。
- 然后調用程序,隨著我們指定的方法的參數,傳遞接收到的對象(數據的指針)。
- 最后,傳遞程序的返回值作為自己的返回值。
編譯器會生成消息函數,你不應該在你寫的代碼中直接調用它。
消息傳遞的關鍵在于有編譯器為每個類和對象生成的結構體。每一個類的結構包含了兩個必要的元素。
- 一個指向父類的指針。
- 一個類分發表,方法選擇器和由方法所確定的類的特定地址關聯起來的元素作為表中的內容。 選擇器
setOrigin::
和setOrigin::
的地址關聯起來,display
和display
的地址關聯起來。
當一個新的對象被創建時,內存被分配,實例變量被初始化,對象變量是一個指向類結構體的指針,這個指針,叫做 isa
,它為這個對象提供了訪問它的類的權限,通過這個類,它可以訪問自身的所有父類。
雖然這不是語言嚴格要求的一部分,但是配合
runtime
系統工作的isa
指針是必不可少的。一個對象,需要有和struct objc_object
(objc/objc.h) 類似的結構,然而,你不需要創建自己的基類,繼承于NSObject
和NSProxy
的對象將自動含有isa
指針。
類和對象的結構如圖 3-1 所示

當給一個對象發消息時,消息函數根據對象的 isa
指針,去類的結構體中的方法分發表中尋找方法選擇器的地址。如果找不到,objc_msgSend
根據這個指針去父類的分發表中尋找。如果一直找不到的話,最終根據繼承鏈,將會找到 NSObject
中。一旦找到了這個 selector
,函數將會調用分發表中的方法,然后把接收到的數據傳遞過去。
這就是方法方法選擇在 runtime
時候的實現,也就是面向對象。方法動態的綁定到消息中。
為了加快消息轉發,runtime
系統將會緩存那些用過的方法和地址。每一個類都有單獨的緩存,它包含了這個類所繼承的方法和在類中定義的方法。在查找分發表前,會先去查找這個緩存表(如果這個方法用過了,那么很有可能會再次使用)。如果這個方法選擇器在緩存中,消息只比函數調用稍慢一點。如果一個程序運行了很長時間,幾乎所有的消息都會從這個緩存中尋找。在程序運行期間,緩存的動態增長可以使其容納新的消息。
使用隱藏參數
當 objc_msgSend
找到了一個方法的實現,他講調用這個實現,然后傳遞消息中的所有參數。同樣也傳遞兩個隱藏參數:
- 接收對象
- 方法選擇器
這些參數,使得方法調用者可以很清晰的直到他們所執行的方法表達式。這兩個參數并沒有在方法的源代碼中定義,而是在代碼被編譯的時候插進去的。
盡管這兩個參數沒有被明確的定義,就像應用接受者的其他實例變量一樣,代碼中可以使用它們。方法中,接受者為 self
,方法本身為 _cmd
,在下邊的例子中, _cmd
指向 strange
方法,self
指向接收 strange
消息的對象。
- strange
{
id target = getTheReceiver();
SEL method = getTheMethod();
if ( target == self || method == _cmd )
return nil;
return [target performSelector:method];
}
在這兩個隱藏參數中,self
更有用,事實上,接受對象的實例參數使得方法定義成為可能。
獲得方法地址
唯一避免動態綁定的方式就是獲得方法的地址,如果這個地址是一個方法的話,直接調用。這種方式一般不常見,如果這個方法要連續調用好多次,你想避免每次消息傳遞的開銷的話,你可以這么做。
當 NSObject
中定義一個方法的時候,你可以用 methodForSelector:
返回的指針去調用函數的實現。methodForSelector:
返回的指針必須轉換為正確的類型。返回值和參數都必須包含其中。
下邊的例子展示了 setFilled
方法如何被調用。
void (*setter)(id, SEL, BOOL);
int i;
setter = (void (*)(id, SEL, BOOL))[target
methodForSelector:@selector(setFilled:)];
for ( i = 0 ; i < 1000 ; i++ )
setter(targetList[i], @selector(setFilled:), YES);
前兩個參數傳遞到程序中的是接受者 self
和方法選擇器 _cmd
,這兩個參數在方法中是被隱藏的,但當作為函數調用的時候,必須顯示指定出來。
使用 methodForSelector:
來避免的動態綁定節省了大部分消息傳遞的時間,然而這個時間節省也只有在重復執行一個函數的時候才顯得有意義,就像上邊的 for
循環一樣。
methodForSelector:
方法是由runtime
系統提供的,這并不是Objective-C
的特性。