筆記記錄:來源于apple的文檔,具體參考:apple文檔
消息傳遞
本章介紹如何將消息表達式轉換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
,它使對象可以訪問其類,并可以通過該類訪問其繼承的所有類。
**注意:** 雖然嚴格來說,語言不是語言的一部分,但
isa對象與Objective-C運行時系統一起使用時需要使用指針。一個對象必須與一個對象“等效”。
struct objc_object(在
objc/objc.h中定義)在結構定義的任何字段中。但是,很少(如果有的話)需要創建自己的根對象,并且從該變量繼承
NSObject或
NSProxy自動具有該
isa變量的對象。
這些類和對象結構的元素如圖3-1所示。
圖3-1 消息傳遞框架
當消息發送到對象時,消息傳遞功能將跟隨對象的 isa
指向類結構的指針,該類結構在調度表中查找方法選擇器。如果無法在其中找到選擇器,則objc_msgSend
跟隨指向超類的指針,并嘗試在其調度表中找到選擇器。連續的失敗導致objc_msgSend
爬升類層次結構,直到到達NSObject
類。找到選擇器后,該函數將調用在表中輸入的方法,并將該方法傳遞給接收對象的數據結構。
這是在運行時選擇方法實現的方式,或者,在面向對象編程的術語中,方法是動態綁定到消息的。
為了加快消息傳遞過程,運行時系統會在使用方法的選擇器和地址時對其進行緩存。每個類都有一個單獨的緩存,并且可以包含繼承的方法以及該類中定義的方法的選擇器。在搜索調度表之前,消息傳遞例程首先檢查接收對象的類的緩存(根據理論,曾經使用過的方法可能會再次使用)。如果方法選擇器在緩存中,則消息傳遞僅比函數調用慢一點。一旦程序運行了足夠長的時間以“預熱”其緩存,幾乎它發送的所有消息都將找到一個緩存方法。緩存在程序運行時動態增長以容納新消息。
使用隱藏參數
當objc_msgSend找到實現方法的過程時,它將調用該過程并將消息中的所有參數傳遞給該過程。它還向過程傳遞兩個隱藏參數:
- 接收對象
- 選擇器 對于方法
這些參數為每個方法實現提供了有關調用它的消息表達式的兩半的明確信息。之所以說它們是“隱藏的”,是因為它們沒有在定義該方法的源代碼中聲明。在編譯代碼時將它們插入到實現中。
盡管未明確聲明這些參數,但是源代碼仍然可以引用它們(就像可以引用接收對象的實例變量一樣)。方法將接收對象稱為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
是這兩個參數中更有用的。實際上,這是使接收對象的實例變量可用于方法定義的方式。
獲取方法地址
規避動態綁定的唯一方法是獲取方法的地址并直接調用它,就好像它是一個函數一樣。在少數情況下,當連續多次執行特定方法,并且您希望避免每次執行該方法時都要避免消息傳遞的開銷時,這可能是合適的。
使用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:
是由Cocoa運行時系統提供的;它不是Objective-C語言本身的功能。
大千世界,求同存異;相遇是緣,相識是份,相知便是“猿糞”(緣分)
From MZou