消息機(jī)制

筆者翻譯自iOS Developer Library Messaging

消息機(jī)制

這章講解了消息表達(dá)式如何被轉(zhuǎn)化成 objc_msgSend 函數(shù)調(diào)用和怎樣通過函數(shù)名關(guān)聯(lián)方法。然后還解釋了你如何利用 objc_msgSend,并且在你需要的時(shí)候如何繞過動(dòng)態(tài)綁定。

objc_msgSend 函數(shù)

在 Object-C 中,消息是直到運(yùn)行時(shí)才綁定相應(yīng)的方法實(shí)現(xiàn)。編譯器將轉(zhuǎn)換消息表達(dá)式,

<pre><code>[receiver message]
</code></pre>

為一個(gè)消息函數(shù)調(diào)用,objc_msgSend。該函數(shù)將接收者(receiver)和在消息中提到的函數(shù)名(也叫函數(shù)選擇器)作為它的兩個(gè)主要參數(shù):

<pre><code>objc_msgSend(receiver, selector)
</code></pre>

消息傳遞的任何其他參數(shù)也將傳遞給 objc_msgSend:

<pre><code>objc_msgSend(receiver, selector, arg1, arg2, ...)
</code></pre>

消息函數(shù)將為動(dòng)態(tài)綁定完成所有必須的事情:

  • 它首先找到函數(shù)選擇器指定的程序塊(函數(shù)方法的實(shí)現(xiàn))。因?yàn)橄嗤姆椒ㄔ诓煌念愔杏胁煌膶?shí)現(xiàn),它找到的正確的程序塊將依賴接收者對(duì)應(yīng)的類。
  • 然后它將消息接收者對(duì)象(一個(gè)指向它的數(shù)據(jù)的指針)和該方法相關(guān)的其他參數(shù)傳給該代碼塊,并調(diào)用該代碼塊。
  • 最后它將該代碼塊的返回值作為它自己的返回值。

提示:編譯器將生成消息函數(shù)的調(diào)用。你不應(yīng)該直接在你所寫的代碼中調(diào)用它。

消息機(jī)制的關(guān)鍵在于編譯器為每個(gè)類和對(duì)象建立的結(jié)構(gòu)。每個(gè)類結(jié)構(gòu)都包含這兩個(gè)必要的元素:

  • 指向父類的指針。
  • 類分配表。這個(gè)表有一些數(shù)據(jù)項(xiàng),他們將方法選擇器和該類定義的方法的地址相對(duì)應(yīng)。方法 setOrigin:: 的選擇器和 setOrigin:: 的地址(方法的實(shí)現(xiàn))相對(duì)應(yīng),方法 display 的選擇器和 display 的地址相對(duì)應(yīng),等等。

當(dāng)一個(gè)對(duì)象被創(chuàng)建,它的內(nèi)存被分配,并且它的實(shí)例變量被初始化。在對(duì)象中的第一個(gè)變量就是指向它的類結(jié)構(gòu)的指針。這個(gè)指針叫做 isa,它使對(duì)象能訪問它所屬的類,并且通過這個(gè)類能訪問所有它繼承的類。

提示:盡管 isa 指針嚴(yán)格上不是語言的一部分,但它是一個(gè)對(duì)象能在 Object-C 運(yùn)行時(shí)系統(tǒng)中工作所必須的。一個(gè)對(duì)象需要在 struct objc_object(定義在 objc/objc.h中)定義的任何字段域上保持對(duì)等。然而,你很少需要去創(chuàng)建自己的根類,而繼承自 NSObject 和 NSProxy 的對(duì)象將自動(dòng)帶有 isa 變量。

這些類和對(duì)象的結(jié)構(gòu)元素如圖所示。

Messaging Framework.jpg

當(dāng)消息被發(fā)送到一個(gè)對(duì)象,消息函數(shù)將沿著對(duì)象的 isa 指針到類結(jié)構(gòu),并在分配表(dispatch table)中查找方法選擇器。如果它不能在這里找到方法選擇器,obj_msgSend 將沿著父類指針并嘗試在父類的分配表中找到方法選擇器。連續(xù)的失敗使 obj_msgSend 沿著類的繼承關(guān)系向上走,直到到達(dá) NSObject 類。一旦它找到了選擇器,消息函數(shù)將調(diào)用分配表中的方法并把接收者對(duì)象的數(shù)據(jù)結(jié)構(gòu)傳遞給它。

這就是方法實(shí)現(xiàn)在運(yùn)行時(shí)被選擇的方式——或者,按面向?qū)ο缶幊痰男性拋碚f就是,方法被動(dòng)態(tài)綁定給消息。

為了加速消息處理,當(dāng)方法被調(diào)用時(shí),運(yùn)行時(shí)系統(tǒng)緩存了選擇器和方法地址。這是一個(gè)相對(duì)于每個(gè)類分開的緩存,它能夠包含繼承的方法和自身定義的方法的選擇器。在搜索分配表之前,消息程序首先檢查接收者對(duì)象所在類的緩存(基于這樣的理論:一個(gè)方法一旦被使用,很可能會(huì)被再次使用)。如果這個(gè)方法選擇器在緩存中,消息機(jī)制只會(huì)稍微比直接函數(shù)調(diào)用慢一點(diǎn)。一旦程序已經(jīng)運(yùn)行了足夠長的時(shí)間去“熱身”它的緩存,幾乎它發(fā)送的所有消息都能找到一個(gè)緩沖的方法。當(dāng)程序運(yùn)行時(shí),緩存將動(dòng)態(tài)地成長去適應(yīng)新消息。

使用隱藏參數(shù)

當(dāng) obj_msgSend 找到實(shí)現(xiàn)方法的程序塊,它調(diào)用該程序塊并把消息中的所有參數(shù)傳遞給代碼塊。它也傳遞兩個(gè)隱藏的參數(shù):

  • 接收者對(duì)象
  • 方法選擇器

這些參數(shù)把調(diào)用它的消息表達(dá)式的兩部分直接信息傳給了每個(gè)方法實(shí)現(xiàn)。它們被認(rèn)為是隱藏的,因?yàn)樗鼈儧]有在定義方法的源代碼中聲明,當(dāng)代碼被編譯時(shí),它們被插入到了函數(shù)的實(shí)現(xiàn)。

盡管這些參數(shù)沒有被直接聲明,源代碼仍然能直接引用它們(就像它能直接引用接收者對(duì)象的實(shí)例變量一樣)。一個(gè)方法把接收者對(duì)象當(dāng)做 self,把自身的選擇器當(dāng)做 _cmd。在下面的例子中,_cmd 是指 strange 方法的選擇器,self 是指接收 strange 消息的對(duì)象。

- strange
{
    id  target = getTheReceiver();
    SEL method = getTheMethod();

    if ( target == self || method == _cmd )
        return nil;
    return [target performSelector:method];
}

self 在這兩個(gè)參數(shù)中更加有用。實(shí)際上,它使得消息的接收者對(duì)象的實(shí)例變量在方法定義中可以被訪問。

得到方法地址

繞過動(dòng)態(tài)綁定的唯一方法就是得到一個(gè)方法的地址并且把它當(dāng)做一個(gè)函數(shù)直接調(diào)用它。在極少的情況下,當(dāng)一個(gè)指定的方法將被連續(xù)執(zhí)行很多次并且你想避免每次方法執(zhí)行時(shí)消息機(jī)制帶來的額外開銷時(shí),繞過動(dòng)態(tài)綁定就變得合理了。

使用在 NSObject 類中定義的方法 methodForSeletor:,你能得到一個(gè)指向方法實(shí)現(xiàn)的程序塊的指針,并能使用這個(gè)指針去調(diào)用該程序塊。methodForSeletor:返回的指針必須轉(zhuǎn)換成合適的函數(shù)類型。返回值和參數(shù)類型都應(yīng)該包含在轉(zhuǎn)換中。

下面的例子展示了實(shí)現(xiàn)了 setFill: 方法的代碼塊如何被調(diào)用:

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);

傳給代碼塊的前兩個(gè)參數(shù)是接收者對(duì)象(self)和方法選擇器(_cmd)。這兩個(gè)參數(shù)被對(duì)象方法語法隱藏,但當(dāng)對(duì)象方法被當(dāng)做一個(gè)普通函數(shù)被調(diào)用時(shí),它們必須被顯式地寫出來。

使用 methodForSelector: 繞過動(dòng)態(tài)綁定節(jié)約了消息機(jī)制需要的大部分時(shí)間。這種方式只有在一個(gè)指定的消息被重復(fù)很多次時(shí)才變得有意義,就像上面例子中的 for 循環(huán)。

提示:methodForSelector: 是 Cocoa 運(yùn)行時(shí)系統(tǒng)提供的,它并不是一個(gè) Object-C 語言自身的特性。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容