本章描述了如何將消息表達(dá)式轉(zhuǎn)換成objc_msgSend函數(shù)調(diào)用,以及如何通過名字引用方法。然后解釋如何利用objc_msgSend以及如何避免動(dòng)態(tài)綁定
objc_msgSend函數(shù)
在Objective-C中,直到運(yùn)行時(shí),消息才會(huì)綁定到方法的實(shí)現(xiàn)。編譯器才會(huì)轉(zhuǎn)換消息表達(dá)式,
<pre><code>[receiver message]
</pre></code>
調(diào)用消息傳遞行數(shù)objc_msgSend。這個(gè)函數(shù)需要接收者和消息中提到的方法名即方法選擇器作為它的兩個(gè)主要參數(shù):
<pre><code>objc_msgSend(receiver, selector)
</pre></code>
消息中傳入的任何參數(shù)都可以在objc_msgSend處理:
<pre><code>objc_msgSend(receiver, selector, arg1, arg2, ...)
</pre></code>
消息傳遞函數(shù)支持動(dòng)態(tài)綁定:
首先,獲取選擇器指向的程序(方法實(shí)現(xiàn))。因?yàn)橄嗤姆椒梢员徊煌念惙謩e實(shí)現(xiàn),獲取的具體程序取決于接收器的類。
然后調(diào)用程序,通過傳遞接收對(duì)象(數(shù)據(jù)指針)以及方法中指定的任何參數(shù)。
最后,它傳遞程序返回值作為自己的返回值。
注意:編譯器生成消息傳遞函數(shù)。不能在代碼中直接調(diào)用。
消息傳遞的關(guān)鍵在于編譯器編譯每個(gè)類和對(duì)象的結(jié)構(gòu)。每個(gè)類結(jié)構(gòu)包括這兩個(gè)基本要素:
指向父類的指針
Dispatch表類。這個(gè)表的記錄可以將方法選擇器與指定類方法的地址關(guān)聯(lián)。
setOrigin::方法的選擇器與setOrigin::地址(程序?qū)崿F(xiàn))有關(guān),display方法的選擇器與的display地址有關(guān),等等
當(dāng)創(chuàng)建一個(gè)新對(duì)象,會(huì)分配內(nèi)存并初始化實(shí)例變量。首先,對(duì)象變量是一個(gè)指向類結(jié)構(gòu)的指針。該指針,稱為isa,通過類,對(duì)象可以訪問該類和該對(duì)象繼承的所有類。
注意:isa指針雖然不是語(yǔ)言嚴(yán)格意義上的一部分,但是是使用Objective-C運(yùn)行時(shí)系統(tǒng)所需的一個(gè)對(duì)象。一個(gè)對(duì)象須“等效于”結(jié)構(gòu)定義中的
struct objc_object(定義于objc/objc.h)。然而,很少需要?jiǎng)?chuàng)建自己的根對(duì)象和繼承自NSObject或NSProxy的對(duì)象,自動(dòng)有isa變量。
類元素和對(duì)象結(jié)構(gòu)如圖3-1所示。

當(dāng)一個(gè)消息發(fā)送到一個(gè)對(duì)象,消息傳遞函數(shù)遵循對(duì)象的isa指針,該指針指向類結(jié)構(gòu),并在dispatch表中查找方法選擇器。如果不能找到選擇器,objc_msgSend則遵循指向父類的指針并試圖在dispatch表找到選擇器。一直找不到選擇器,objc_msgSend將一直查找類的層次結(jié)構(gòu),直到NSObject類。一旦定位到選擇器,函數(shù)將調(diào)用表中的方法,并將其傳遞到接收對(duì)象的數(shù)據(jù)結(jié)構(gòu)。
運(yùn)行時(shí)選擇以這種方式實(shí)現(xiàn)方法。或者以面向?qū)ο缶幊绦g(shù)語(yǔ)來說,該方法是動(dòng)態(tài)綁定到消息。
為了加快消息傳遞過程,運(yùn)行時(shí)系統(tǒng)緩存使用的方法的選擇器和地址。每個(gè)類有一個(gè)單獨(dú)的緩存,可以包含繼承方法和類中定義方法的選擇器。在搜索dispatch表之前,消息傳遞程序首先檢查接收對(duì)象類(理論上,是有可能再次使用的方法)的緩存。如果方法選擇器在緩存中,消息傳遞稍微比函數(shù)調(diào)用慢。一旦一個(gè)程序運(yùn)行足夠長(zhǎng)時(shí)間來“熱身”緩存,幾乎所有發(fā)送的消息都能找到緩存方法。在程序運(yùn)行時(shí),緩存能動(dòng)態(tài)適應(yīng)新消息。
使用隱藏的參數(shù)
當(dāng)objc_msgSend發(fā)現(xiàn)實(shí)現(xiàn)方法的程序,它調(diào)用程序,并傳遞消息中所有的參數(shù)。也傳遞兩個(gè)隱藏參數(shù)到程序:
接收對(duì)象
方法選擇器
這些參數(shù)為每個(gè)方法實(shí)現(xiàn)提供明確信息,這些信息關(guān)于調(diào)用它們的消息表達(dá)式。它們被認(rèn)為是“隱藏”的,因?yàn)榉椒ǘx代碼中未聲明它們。當(dāng)編譯代碼時(shí),它們插入到實(shí)現(xiàn)中。
盡管這些參數(shù)沒有顯式的聲明,源代碼仍然可以引用它們(就像它可以引用接收對(duì)象的實(shí)例變量)。方法引用接收對(duì)象作為self,以及自己的選擇器作為_cmd。在下面的例子中,_cmd引用strange 方法的選擇器,self引用接收一個(gè)strange 消息的對(duì)象。
<pre><code>
-
strange
{
id target = getTheReceiver();
SEL method = getTheMethod();if ( target == self || method == _cmd )
return nil;
return [target performSelector:method];
}
</pre></code>
Self對(duì)兩個(gè)參數(shù)更加有用。實(shí)際上,接收對(duì)象的實(shí)例變量可用于方法定義。
獲取方法地址
避免動(dòng)態(tài)綁定的唯一方法是獲取方法的地址并且直接調(diào)用它,就好像它是個(gè)函數(shù)。當(dāng)一個(gè)特定的方法多次連續(xù)執(zhí)行,并且你希望每次執(zhí)行該方法時(shí)避免消息傳遞開銷,在這種極少數(shù)的情況下,該方法可行。
NSObject類中定義一個(gè)methodForSelector:方法,可以訪問指向?qū)崿F(xiàn)方法程序的指針,然后使用指針調(diào)用該程序。methodForSelector:指針的返回值必須指向合適的函數(shù)類型。必須包含返回值和參數(shù)類型。
下面的例子展示了程序如何實(shí)現(xiàn)setFilled: 方法:
<pre><code>
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);
</pre></code>
前兩個(gè)參數(shù)傳遞給接收對(duì)象(self)的程序和方法選擇器(_cmd)。這些參數(shù)在方法語(yǔ)法中是隱藏的,但當(dāng)該方法當(dāng)成函數(shù)調(diào)用時(shí),必須是顯式的。
使用methodForSelector:方法避免動(dòng)態(tài)綁定節(jié)省了消息轉(zhuǎn)發(fā)所需的大部分時(shí)間。然而,只有在特定消息重復(fù)多次的情況下,如上面的for循環(huán),節(jié)省時(shí)間才會(huì)有重要意義。
注意:Cocoa運(yùn)行時(shí)系統(tǒng)提供
methodForSelector:方法,該方法并不是Objective-C 語(yǔ)言本身的特性。