筆記記錄:來源于apple的文檔,具體參考:apple文檔
消息傳遞
本章介紹如何將消息表達(dá)式轉(zhuǎn)換objc_msgSend函數(shù)調(diào)用,以及如何按名稱引用方法。然后,它說明了如何利用objc_msgSend,以及(如果需要)如何規(guī)避動(dòng)態(tài)綁定。
objc_msgSend函數(shù)
在Objective-C中,消息直到運(yùn)行時(shí)才綁定到方法實(shí)現(xiàn)。編譯器轉(zhuǎn)換一個(gè)消息表達(dá)式,
[receiver message]
調(diào)用消息傳遞功能, objc_msgSend。此功能需要接收器 消息中提到的方法的名稱(即方法選擇器)作為其兩個(gè)主要參數(shù):
objc_msgSend(receiver, selector)
所有的屬性也都是通過objc_msgSend進(jìn)行轉(zhuǎn)發(fā)的:
objc_msgSend(receiver, selector, arg1, arg2, ...)
消息傳遞功能完成了動(dòng)態(tài)綁定所需的一切:
- 它首先找到選擇器的過程(方法實(shí)現(xiàn))指。由于相同的方法可以通過不同的類不同地實(shí)現(xiàn),因此它找到的精確過程取決于接收者的類。
- 然后,它將調(diào)用該過程,并將接收對(duì)象(指向其數(shù)據(jù)的指針)以及為該方法指定的所有參數(shù)傳遞給該過程。
- 最后,它將過程的返回值作為自己的返回值傳遞。
注意: 編譯器會(huì)生成對(duì)消息傳遞功能的調(diào)用。您永遠(yuǎn)不需要在編寫的代碼中直接調(diào)用它。
消息傳遞的關(guān)鍵在于編譯器為每個(gè)類和對(duì)象構(gòu)建的結(jié)構(gòu)。每個(gè)類結(jié)構(gòu)都包含以下兩個(gè)基本元素:
指向超類的指針。
類調(diào)度表。該表具有將方法選擇器與其所標(biāo)識(shí)的方法的類特定地址相關(guān)聯(lián)的條目。
setOrigin::方法的選擇器與(實(shí)現(xiàn)的過程)的地址相關(guān)聯(lián),方法setOrigin::的選擇器display與display的地址相關(guān)聯(lián),依此類推。
創(chuàng)建新對(duì)象時(shí),將為其分配內(nèi)存,并初始化其實(shí)例變量。對(duì)象變量中的第一個(gè)是指向其類結(jié)構(gòu)的指針。該指針稱為isa,它使對(duì)象可以訪問其類,并可以通過該類訪問其繼承的所有類。
**注意:** 雖然嚴(yán)格來說,語言不是語言的一部分,但isa對(duì)象與Objective-C運(yùn)行時(shí)系統(tǒng)一起使用時(shí)需要使用指針。一個(gè)對(duì)象必須與一個(gè)對(duì)象“等效”。struct objc_object(在objc/objc.h中定義)在結(jié)構(gòu)定義的任何字段中。但是,很少(如果有的話)需要?jiǎng)?chuàng)建自己的根對(duì)象,并且從該變量繼承NSObject或NSProxy自動(dòng)具有該isa變量的對(duì)象。
這些類和對(duì)象結(jié)構(gòu)的元素如圖3-1所示。
圖3-1 消息傳遞框架

當(dāng)消息發(fā)送到對(duì)象時(shí),消息傳遞功能將跟隨對(duì)象的 isa指向類結(jié)構(gòu)的指針,該類結(jié)構(gòu)在調(diào)度表中查找方法選擇器。如果無法在其中找到選擇器,則objc_msgSend跟隨指向超類的指針,并嘗試在其調(diào)度表中找到選擇器。連續(xù)的失敗導(dǎo)致objc_msgSend爬升類層次結(jié)構(gòu),直到到達(dá)NSObject類。找到選擇器后,該函數(shù)將調(diào)用在表中輸入的方法,并將該方法傳遞給接收對(duì)象的數(shù)據(jù)結(jié)構(gòu)。
這是在運(yùn)行時(shí)選擇方法實(shí)現(xiàn)的方式,或者,在面向?qū)ο缶幊痰男g(shù)語中,方法是動(dòng)態(tài)綁定到消息的。
為了加快消息傳遞過程,運(yùn)行時(shí)系統(tǒng)會(huì)在使用方法的選擇器和地址時(shí)對(duì)其進(jìn)行緩存。每個(gè)類都有一個(gè)單獨(dú)的緩存,并且可以包含繼承的方法以及該類中定義的方法的選擇器。在搜索調(diào)度表之前,消息傳遞例程首先檢查接收對(duì)象的類的緩存(根據(jù)理論,曾經(jīng)使用過的方法可能會(huì)再次使用)。如果方法選擇器在緩存中,則消息傳遞僅比函數(shù)調(diào)用慢一點(diǎn)。一旦程序運(yùn)行了足夠長的時(shí)間以“預(yù)熱”其緩存,幾乎它發(fā)送的所有消息都將找到一個(gè)緩存方法。緩存在程序運(yùn)行時(shí)動(dòng)態(tài)增長以容納新消息。
使用隱藏參數(shù)
當(dāng)objc_msgSend找到實(shí)現(xiàn)方法的過程時(shí),它將調(diào)用該過程并將消息中的所有參數(shù)傳遞給該過程。它還向過程傳遞兩個(gè)隱藏參數(shù):
- 接收對(duì)象
- 選擇器 對(duì)于方法
這些參數(shù)為每個(gè)方法實(shí)現(xiàn)提供了有關(guān)調(diào)用它的消息表達(dá)式的兩半的明確信息。之所以說它們是“隱藏的”,是因?yàn)樗鼈儧]有在定義該方法的源代碼中聲明。在編譯代碼時(shí)將它們插入到實(shí)現(xiàn)中。
盡管未明確聲明這些參數(shù),但是源代碼仍然可以引用它們(就像可以引用接收對(duì)象的實(shí)例變量一樣)。方法將接收對(duì)象稱為self,并將其作為自己的選擇器 _cmd。在下面的示例中,_cmd引用strange方法的選擇器和self接收strange消息的對(duì)象
- (void)strange {
id target = getTheReceiver();
SEL method = getTheMethod();
if ( target == self || method == _cmd) {
return nil;
}
return [target performSelector:method];
}
self是這兩個(gè)參數(shù)中更有用的。實(shí)際上,這是使接收對(duì)象的實(shí)例變量可用于方法定義的方式。
獲取方法地址
規(guī)避動(dòng)態(tài)綁定的唯一方法是獲取方法的地址并直接調(diào)用它,就好像它是一個(gè)函數(shù)一樣。在少數(shù)情況下,當(dāng)連續(xù)多次執(zhí)行特定方法,并且您希望避免每次執(zhí)行該方法時(shí)都要避免消息傳遞的開銷時(shí),這可能是合適的。
使用NSObject類中定義的方法,methodForSelector:,您可以要求一個(gè)指向?qū)崿F(xiàn)方法的過程的指針,然后使用該指針來調(diào)用該過程。methodForSelector:返回的指針必須仔細(xì)轉(zhuǎn)換為正確的函數(shù)類型。返回類型和參數(shù)類型都應(yīng)包含在強(qiáng)制類型轉(zhuǎn)換中。
下面的示例顯示了如何setFilled:調(diào)用實(shí)現(xiàn)該方法的過程:
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)。這些參數(shù)隱藏在方法語法中,但是在將方法作為函數(shù)調(diào)用時(shí)必須將其明確顯示。
使用methodForSelector:規(guī)避動(dòng)態(tài)綁定可以節(jié)省消息傳遞所需的大部分時(shí)間。但是,僅在重復(fù)多次重復(fù)一條特定消息的情況下,這種節(jié)省才是可觀的,如for上面所示的循環(huán)。
請注意,這methodForSelector:是由Cocoa運(yùn)行時(shí)系統(tǒng)提供的;它不是Objective-C語言本身的功能。
大千世界,求同存異;相遇是緣,相識(shí)是份,相知便是“猿糞”(緣分)
From MZou