筆記記錄:來源于apple的文檔实胸,具體參考:apple文檔
消息傳遞
本章介紹如何將消息表達(dá)式轉(zhuǎn)換objc_msgSend函數(shù)調(diào)用,以及如何按名稱引用方法。然后导街,它說明了如何利用objc_msgSend
,以及(如果需要)如何規(guī)避動態(tài)綁定纤子。
objc_msgSend函數(shù)
在Objective-C中搬瑰,消息直到運行時才綁定到方法實現(xiàn)款票。編譯器轉(zhuǎn)換一個消息表達(dá)式,
[receiver message]
調(diào)用消息傳遞功能泽论, objc_msgSend艾少。此功能需要接收器 消息中提到的方法的名稱(即方法選擇器)作為其兩個主要參數(shù):
objc_msgSend(receiver, selector)
所有的屬性也都是通過objc_msgSend
進(jìn)行轉(zhuǎn)發(fā)的:
objc_msgSend(receiver, selector, arg1, arg2, ...)
消息傳遞功能完成了動態(tài)綁定所需的一切:
- 它首先找到選擇器的過程(方法實現(xiàn))指。由于相同的方法可以通過不同的類不同地實現(xiàn)翼悴,因此它找到的精確過程取決于接收者的類缚够。
- 然后,它將調(diào)用該過程鹦赎,并將接收對象(指向其數(shù)據(jù)的指針)以及為該方法指定的所有參數(shù)傳遞給該過程谍椅。
- 最后,它將過程的返回值作為自己的返回值傳遞古话。
注意: 編譯器會生成對消息傳遞功能的調(diào)用雏吭。您永遠(yuǎn)不需要在編寫的代碼中直接調(diào)用它。
消息傳遞的關(guān)鍵在于編譯器為每個類和對象構(gòu)建的結(jié)構(gòu)陪踩。每個類結(jié)構(gòu)都包含以下兩個基本元素:
指向超類的指針杖们。
類調(diào)度表。該表具有將方法選擇器與其所標(biāo)識的方法的類特定地址相關(guān)聯(lián)的條目肩狂。
setOrigin::
方法的選擇器與(實現(xiàn)的過程)的地址相關(guān)聯(lián)摘完,方法setOrigin::
的選擇器display
與display
的地址相關(guān)聯(lián),依此類推傻谁。
創(chuàng)建新對象時孝治,將為其分配內(nèi)存,并初始化其實例變量栅螟。對象變量中的第一個是指向其類結(jié)構(gòu)的指針荆秦。該指針稱為isa
,它使對象可以訪問其類力图,并可以通過該類訪問其繼承的所有類。
**注意:** 雖然嚴(yán)格來說掺逼,語言不是語言的一部分吃媒,但
isa對象與Objective-C運行時系統(tǒng)一起使用時需要使用指針。一個對象必須與一個對象“等效”吕喘。
struct objc_object(在
objc/objc.h中定義)在結(jié)構(gòu)定義的任何字段中赘那。但是,很少(如果有的話)需要創(chuàng)建自己的根對象氯质,并且從該變量繼承
NSObject或
NSProxy自動具有該
isa變量的對象募舟。
這些類和對象結(jié)構(gòu)的元素如圖3-1所示。
圖3-1 消息傳遞框架
當(dāng)消息發(fā)送到對象時闻察,消息傳遞功能將跟隨對象的 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)用在表中輸入的方法,并將該方法傳遞給接收對象的數(shù)據(jù)結(jié)構(gòu)缨睡。
這是在運行時選擇方法實現(xiàn)的方式鸟悴,或者,在面向?qū)ο缶幊痰男g(shù)語中奖年,方法是動態(tài)綁定到消息的细诸。
為了加快消息傳遞過程,運行時系統(tǒng)會在使用方法的選擇器和地址時對其進(jìn)行緩存拾并。每個類都有一個單獨的緩存揍堰,并且可以包含繼承的方法以及該類中定義的方法的選擇器。在搜索調(diào)度表之前嗅义,消息傳遞例程首先檢查接收對象的類的緩存(根據(jù)理論屏歹,曾經(jīng)使用過的方法可能會再次使用)。如果方法選擇器在緩存中之碗,則消息傳遞僅比函數(shù)調(diào)用慢一點蝙眶。一旦程序運行了足夠長的時間以“預(yù)熱”其緩存,幾乎它發(fā)送的所有消息都將找到一個緩存方法褪那。緩存在程序運行時動態(tài)增長以容納新消息幽纷。
使用隱藏參數(shù)
當(dāng)objc_msgSend找到實現(xiàn)方法的過程時,它將調(diào)用該過程并將消息中的所有參數(shù)傳遞給該過程博敬。它還向過程傳遞兩個隱藏參數(shù):
- 接收對象
- 選擇器 對于方法
這些參數(shù)為每個方法實現(xiàn)提供了有關(guān)調(diào)用它的消息表達(dá)式的兩半的明確信息友浸。之所以說它們是“隱藏的”,是因為它們沒有在定義該方法的源代碼中聲明偏窝。在編譯代碼時將它們插入到實現(xiàn)中收恢。
盡管未明確聲明這些參數(shù),但是源代碼仍然可以引用它們(就像可以引用接收對象的實例變量一樣)祭往。方法將接收對象稱為self
伦意,并將其作為自己的選擇器 _cmd
。在下面的示例中硼补,_cmd
引用strange
方法的選擇器和self
接收strange
消息的對象
- (void)strange {
id target = getTheReceiver();
SEL method = getTheMethod();
if ( target == self || method == _cmd) {
return nil;
}
return [target performSelector:method];
}
self
是這兩個參數(shù)中更有用的驮肉。實際上,這是使接收對象的實例變量可用于方法定義的方式已骇。
獲取方法地址
規(guī)避動態(tài)綁定的唯一方法是獲取方法的地址并直接調(diào)用它离钝,就好像它是一個函數(shù)一樣票编。在少數(shù)情況下,當(dāng)連續(xù)多次執(zhí)行特定方法奈辰,并且您希望避免每次執(zhí)行該方法時都要避免消息傳遞的開銷時栏妖,這可能是合適的。
使用NSObject
類中定義的方法奖恰,methodForSelector:
吊趾,您可以要求一個指向?qū)崿F(xiàn)方法的過程的指針,然后使用該指針來調(diào)用該過程瑟啃。methodForSelector:
返回的指針必須仔細(xì)轉(zhuǎn)換為正確的函數(shù)類型论泛。返回類型和參數(shù)類型都應(yīng)包含在強制類型轉(zhuǎn)換中。
下面的示例顯示了如何setFilled:
調(diào)用實現(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);
傳遞給過程的前兩個參數(shù)是接收對象(self
)和方法選擇器(_cmd
)蛹屿。這些參數(shù)隱藏在方法語法中屁奏,但是在將方法作為函數(shù)調(diào)用時必須將其明確顯示。
使用methodForSelector:
規(guī)避動態(tài)綁定可以節(jié)省消息傳遞所需的大部分時間错负。但是坟瓢,僅在重復(fù)多次重復(fù)一條特定消息的情況下,這種節(jié)省才是可觀的犹撒,如for
上面所示的循環(huán)折联。
請注意,這methodForSelector:
是由Cocoa運行時系統(tǒng)提供的识颊;它不是Objective-C語言本身的功能诚镰。
大千世界,求同存異祥款;相遇是緣清笨,相識是份,相知便是“猿糞”(緣分)
From MZou