Runtime的入門與應用之五-消息發送與轉發

消息發送

消息發送舉例:下面這個OC代碼

[person read:book];
  • 1
  • 2

會被編譯成:

objc_msgSend(person, @selector(read:), book);
  • 1
  • 2

objc_msgSend的具體流程如下:

  1. 通過isa指針找到所屬類
  2. 查找類的cache列表, 如果沒有則下一步
  3. 查找類的”方法列表”
  4. 如果能找到與選擇子名稱相符的方法, 就跳至其實現代碼
  5. 找不到, 就沿着繼承體系繼續向上查找
  6. 如果能找到與選擇子名稱相符的方法, 就跳至其實現代碼
  7. 找不到, 執行”消息轉發”.

消息轉發

上面我們提到, 如果到最後都找不到, 就會來到消息轉發,消息轉發的流程如下:

  1. 動態方法解析 : 先問接收者所屬的類, 你看能不能動態添加個方法來處理這個”未知的消息”? 如果能, 則消息轉發結束.
  2. 備胎(後備接收者) : 請接收者看看有沒有其他對象能處理這條消息? 如果有, 則把消息轉給那個對象, 消息轉發結束.
  3. 消息簽名 : 這裏會要求你返回一個消息簽名, 如果返回nil, 則消息轉發結束.
  4. 完整的消息轉發 : 備胎都搞不定了, 那就只能把該消息相關的所有細節都封裝到一個NSInvocation對象, 再問接收者一次, 快想辦法把這個搞定了. 到了這個地步如果還無法處理, 消息轉發機制也無能爲力了。

1. 動態方法解析

對象在收到無法解讀的消息後, 首先調用其所屬類的這個類方法 :

+ (BOOL)resolveInstanceMethod:(SEL)selector
// selector : 那個未知的選擇子
// 返回YES則結束消息轉發
// 返回NO則進入備胎
  • 1
  • 2
  • 3
  • 4

假如尚未實現的方法不是實例方法而是類方法, 則會調用另一個方法resolveClassMethod:

2. 備胎

動態方法解析失敗, 則調用這個方法

- (id)forwardingTargetForSelector:(SEL)selector
// selector : 那個未知的消息
// 返回一個能響應該未知選擇子的備胎對象
  • 1
  • 2
  • 3

通過備胎這個方法, 可以用”組合”來模擬出”多重繼承”.

3. 消息簽名

備胎搞不定, 這個方法就準備要被包裝成一個NSInvocation對象, 在這裏要先返回一個方法簽名

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
// NSMethodSignature : 該selector對應的方法簽名
  • 1
  • 2

4. 完整的消息轉發

給接收者最後一次機會把這個方法處理了, 搞不定就直接程序崩潰!

- (void)forwardInvocation:(NSInvocation *)invocation
// invocation : 封裝了與那條尚未處理的消息相關的所有細節的對象
  • 1
  • 2

在這裏能做的比較現實的事就是 : 在觸發消息前, 先以某種方式改變消息內容, 比如追加另外一個參數, 或是改變消息等等. 實現此方法時, 如果發現某調用操作不應該由本類處理, 可以調用超類的同名方法. 則繼承體系中的每個類都有機會處理該請求, 直到NSObject. 如果NSObject搞不定, 則還會調用doesNotRecognizeSelector:來拋出異常, 此時你就會在控制檯看到那熟悉的unrecognized selector sent to instance..

消息轉發流程

上面這4個方法均是模板方法,開發者可以override,由runtime來調用。最常見的實現消息轉發,就是重寫方法3和4,忽略這個消息或者代理給其他對象.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章