深入研究Runtime(2) - 發送消息Messaging - 簡書

ywkm2020 8年前發布 | 10K 次閱讀 Runtime iOS開發 移動開發 IOS

消息機制

在Objective-C中,最重要的就是消息發送機制。本文從兩個方面深入學習該機制:

  1. 消息表達式如何轉換為調用objc_msgSend函數 && 如何通過方法名發送消息?
  2. 如何利用objc_msgSend函數 && 必要時如何避免動態綁定?

objc_msgSend

在Objective-C語言里,消息直到運行時才會綁定到具體的方法實現。編譯器把消息表達式

[receiver message]

轉化為調用消息發送函數objc_msgSend函數。該函數有兩個主要的參數:

  • receiver:消息接收者
  • selector:方法選擇器

objc_msgSend(receiver, selector)

如果消息表達式有參數,則轉化如下:

objc_msgSend(receiver, selector, arg1, arg2, ...)

動態綁定就是在消息發送函數objc_msgSend里實現的

  1. 根據receiver的類找到selector指向的方法的實現
  2. 調用找到的方法實現,把消息接收對象( 實際上傳遞的是對象數據的指針 )和方法的相關參數傳遞進去
  3. 將執行方法實現后的返回值返回出去

其實當一個對象被創建、分配好內存、初始化實例變量時,第一個被創建的變量就是指向類結構體的指針( isa 指針 )

note:一個對象與在運行時系統上工作,isa 指針是必須有的。定義一個對象的結構體,必須與struct objc_object { Class isa; }相符合( 在objc/objc.h中定義 )。但是我們很少去定義這個結構體,因為NSObjectNSProxy中的 allocallocWithZone: 方法會調用 class_createInstance 生成 objc_object

如下整個消息發送的流程框架:實現了上面(1.)步驟

深入研究Runtime(2) - 發送消息Messaging - 簡書

Messaging.png

  • 在上面內容中已經知道,調用發送消息函數時,已經獲得對象數據的指針。通過該指針,我們可以找到對象內存地址,從而找到isa指針。由上圖可知,獲得isa指針后,我們可以遍歷相關的類對象。每個類對象中都包含了一個方法列表,配合已知receiver的類,則可以定位到要調用的方法( 當前類對象方法列表沒有,則尋找父類的,直到找到或者到NSObject為止 )。

  • 上述整個過程就是動態綁定的過程( OOP編程 )

補充:

  • 對象方法存儲在類對象的方法列表當中,而類方法存儲在元類( metaclass )對象的方法列表當中
  • 其實為了加快整個消息發送的過程,運行時系統緩存了我們曾經使用的方法名和方法地址( 如方法列表中的 selector ... address )。每個類的緩存是分開的,包括重寫了的父類方法。在查找方法列表之前,會先查找緩存。如果方法已經緩存,發送消息只是比直接調用函數慢一點。

使用隱藏參數

由上面內容可以,objc_msgSend函數實現中,當找到方法的實現后,會執行該實現,并傳入相關的對象和參數。而相關對象就是:

  1. 消息接收的對象 ( self )
  2. 方法的動態指針 ( _cmd )

note:為什么叫隱藏參數?因為在方法聲明實現時,在參數列表中,我們沒有明顯的寫出來

如下( strange方法動態綁定大概實現猜測 ):

  - strange {
  id target = getTheReceiver();
  SEL method = getTheMethod();
  if ( target == self || method == _cmd )
    return nil; 
    return [target performSelector:method];  // 根據方法名調用方法
 }

獲取方法的地址

想要避免動態綁定,唯一方法就是獲得獲得方法地址,然后想函數一樣調用它。一般這種情況很少用,例如想大量重復調用方法,而你又減少動態綁定的資源開銷。

  • 使用 NSObjectmethodForSelector: 方法可以根據方法名獲得方法的地址

例子: 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
     

 本文由用戶 ywkm2020 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!