消息發送
消息發送舉例:下面這個OC代碼
[person read:book];
- 1
- 2
會被編譯成:
objc_msgSend(person, @selector(read:), book);
- 1
- 2
objc_msgSend的具體流程如下:
- 通過isa指針找到所屬類
- 查找類的cache列表, 如果沒有則下一步
- 查找類的”方法列表”
- 如果能找到與選擇子名稱相符的方法, 就跳至其實現代碼
- 找不到, 就沿着繼承體系繼續向上查找
- 如果能找到與選擇子名稱相符的方法, 就跳至其實現代碼
- 找不到, 執行”消息轉發”.
消息轉發
上面我們提到, 如果到最後都找不到, 就會來到消息轉發,消息轉發的流程如下:
- 動態方法解析 : 先問接收者所屬的類, 你看能不能動態添加個方法來處理這個”未知的消息”? 如果能, 則消息轉發結束.
- 備胎(後備接收者) : 請接收者看看有沒有其他對象能處理這條消息? 如果有, 則把消息轉給那個對象, 消息轉發結束.
- 消息簽名 : 這裏會要求你返回一個消息簽名, 如果返回nil, 則消息轉發結束.
- 完整的消息轉發 : 備胎都搞不定了, 那就只能把該消息相關的所有細節都封裝到一個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,忽略這個消息或者代理給其他對象.