深入研究Runtime(2) - 發送消息Messaging - 簡書
消息機制
在Objective-C中,最重要的就是消息發送機制。本文從兩個方面深入學習該機制:
- 消息表達式如何轉換為調用
objc_msgSend
函數 && 如何通過方法名
發送消息? - 如何利用
objc_msgSend
函數 && 必要時如何避免動態綁定?
objc_msgSend
在Objective-C語言里,消息直到運行時
才會綁定到具體的方法實現。編譯器把消息表達式
[receiver message]
轉化為調用消息發送函數objc_msgSend
函數。該函數有兩個主要的參數:
- receiver:消息接收者
- selector:方法選擇器
objc_msgSend(receiver, selector)
如果消息表達式有參數,則轉化如下:
objc_msgSend(receiver, selector, arg1, arg2, ...)
動態綁定就是在消息發送函數objc_msgSend
里實現的
- 根據
receiver的類
找到selector
指向的方法的實現 - 調用找到的方法實現,把消息
接收對象
( 實際上傳遞的是對象數據的指針 )和方法的相關參數
傳遞進去 - 將執行方法實現后的返回值返回出去
其實當一個對象
被創建、分配好內存、初始化實例變量時,第一個被創建的變量就是指向類結構體
的指針( isa 指針 )
note:一個對象與在運行時系統上工作,isa 指針是必須有的。定義一個對象的結構體,必須與
struct objc_object { Class isa; }
相符合( 在objc/objc.h中定義 )。但是我們很少去定義這個結構體,因為NSObject
和NSProxy
中的alloc
和allocWithZone:
方法會調用class_createInstance
生成objc_object
如下整個消息發送的流程框架:實現了上面(1.)步驟
Messaging.png
-
在上面內容中已經知道,調用
發送消息函數
時,已經獲得對象數據的指針
。通過該指針,我們可以找到對象內存地址,從而找到isa指針
。由上圖可知,獲得isa指針后,我們可以遍歷相關的類對象。每個類對象中都包含了一個方法列表
,配合已知receiver的類
,則可以定位到要調用的方法( 當前類對象方法列表沒有,則尋找父類的,直到找到或者到NSObject為止 )。 -
上述整個過程就是
動態綁定
的過程( OOP編程 )
補充:
對象方法
存儲在類對象
的方法列表當中,而類方法
存儲在元類( metaclass )對象
的方法列表當中- 其實為了加快整個消息發送的過程,運行時系統
緩存
了我們曾經使用的方法名和方法地址( 如方法列表中的 selector ... address )。每個類的緩存是分開的,包括重寫了的父類方法。在查找方法列表之前,會先查找緩存。如果方法已經緩存,發送消息只是比直接調用函數慢一點。
使用隱藏參數
由上面內容可以,objc_msgSend
函數實現中,當找到方法的實現后,會執行該實現,并傳入相關的對象和參數。而相關對象就是:
- 消息接收的對象 ( self )
- 方法的動態指針 ( _cmd )
note:為什么叫隱藏參數?因為在方法聲明實現時,在參數列表中,我們沒有明顯的寫出來
如下( strange方法動態綁定大概實現猜測 ):
- strange {
id target = getTheReceiver();
SEL method = getTheMethod();
if ( target == self || method == _cmd )
return nil;
return [target performSelector:method]; // 根據方法名調用方法
}
獲取方法的地址
想要避免動態綁定,唯一方法就是獲得獲得方法地址,然后想函數一樣調用它。一般這種情況很少用,例如想大量重復調用
方法,而你又減少動態綁定的資源開銷。
- 使用
NSObject
的methodForSelector:
方法可以根據方法名獲得方法的地址
例子: setFilled: (BOOL)
重復調用
// - (IMP) methodForSelector:(SEL)aSelector 返回的是類型是IMP
// id (*IMP)(id, SEL, ...):方法實現對應的C語言函數的函數指針
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);</code></pre>
- 定義
IMP
函數指針時,隱藏參數不能省略,方法參數需必須傳入。
- id:消息接收對象
- SEL:方法選擇器
- BOOL:
setFilled:
的參數
</li>
</ul>
其實如果不是大量的循環發送消息,這種資源的浪費是很少的,所以沒必要去主動去避免動態綁定。
文/老譚是誰(簡書作者)
來源:http://www.jianshu.com/p/b2b1e4d04b81