爲何Objective-C的消息轉發要設計三個階段?

咱們知道,在Objective-C中若是給一個對象發送一條它沒法處理的消息,就會進入下圖描述的消息轉發(Message Forwarding)流程,可是爲何要設計這麼複雜的流程呢?html

消息轉發流程

消息轉發能夠分爲三個階段,不一樣資料中每一個階段的名稱不太同樣,蘋果的官方文檔也沒有明確指出這三個階段,因此這裏階段的名稱僅供參考。數據庫

下面咱們就經過詳細解讀每一個階段來回答開篇提出的問題。bash

第一階段:動態方法解析(Dynamic Method Resolution)

有些狀況下,你但願可以爲一個方法動態地提供實現。例如,Objective-C中能夠將一個屬性聲明爲@dynamicapp

@dynamic propertyName;
複製代碼

這樣你就告訴編譯器,與這個屬性相關聯的setter和getter方法會被動態添加。編譯器就不會自動爲你建立setter和getter以及對應的成員變量(instance variable或叫Ivar)。ide

你能夠經過實現方法resolveInstanceMethod:resolveClassMethod:爲指定的selector動態添加實現。函數

一個 Objective-C方法不過是一個C函數,這個函數最少有兩個參數——self和_cmd。你能夠經過class_addMethod函數把一個函數添加到一個類中。你須要提供相似下面的函數:ui

void dynamicMethodIMP(id self, SEL _cmd) {
    // implementation ....
}
複製代碼

在消息轉發的流程中,使用resolveInstanceMethod:動態地將一個函數添加爲一個類的方法:spa

@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
    if (aSEL == @selector(resolveThisMethodDynamically)) {
          class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
          return YES;
    }
    return [super resolveInstanceMethod:aSEL];
}
@end
複製代碼

class_addMethod最後一個參數叫作types,是一個描述方法的參數類型的字符串。設計

v表明void,@表明對象或者說id類型,:表明方法選擇器SEL。具體參見:Objective-C Runtime Programming Guide->Type Encodingscode

上面的dynamicMethodIMP,返回值是void,兩個入參分別是id和SEL,因此描述這個方法的參數類型的字符串就是"v@:"

這個階段的意義是爲一個類動態提供方法實現。嚴格來講,還沒進入消息轉發流程。respondsToSelector:instancesRespondToSelector:也會調用resolveInstanceMethod:。也就是說,若是resolveInstanceMethod:返回了YES,那麼respondsToSelector:instancesRespondToSelector:都會返回YES。

在CoreData中,有些屬性標記爲@dynamic,這些屬性的值背後是經過數據庫來更新和獲取的,並不須要一個成員變量。因此就會爲這些屬性的setter和getter方法實現resolveInstanceMethod:,返回YES,並經過數據庫來設置或者獲取該屬性的值。

第二階段:替換消息接收者(快速轉發)

若是第一階段resolveInstanceMethod:返回了NO,就會調用forwardingTargetForSelector:詢問是否把消息轉發給另外一個對象。這至關於把objc_msgSend的第一個參數改成另外一個對象,消息的接收者就改變了。

- (id)forwardingTargetForSelector:(SEL)aSelector {
    return someOtherObject;
}
複製代碼

第三階段:徹底消息轉發機制

若是第二階段的forwardingTargetForSelector:返回了nil,這就進入了所謂徹底消息轉發的機制。

首先調用methodSignatureForSelector:爲要轉發的消息返回正確的簽名:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if ([NSStringFromSelector(aSelector) isEqualToString:@"foo"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}
複製代碼

返回了正確的簽名後,就會調用forwardInvocation:將消息轉發

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"forwardInvocation");
    SomeOtherObject *someOtherObject = [SomeOtherObject new];
    if ([someOtherObject respondsToSelector:[anInvocation selector]]) {
        [anInvocation invokeWithTarget:someOtherObject];
    } else {
        [super forwardInvocation:anInvocation];
    }
}
複製代碼

上面代碼是將消息轉發給其餘對象,其實這與第二階段中示例代碼作的事情是同樣的。區別就在於這個階段會有一個NSInvocation對象。

NSInvocation是一個用來存儲和轉發消息的對象。它包含了一個Objective-C消息的全部元素:一個target,一個selector,參數和返回值。每一個元素均可以被直接設置。

因此不一樣與第二階段,在這個階段你能夠:

  • 把消息存儲,在你以爲合適的時機轉發出去,或者不處理這個消息。
  • 修改消息的target,selector,參數等
  • 屢次轉發這個消息,轉發給多個對象

顯然在這個階段,你能夠對一個OC消息作更多的事情。

總結

第一階段意義在於動態添加方法實現,第二階段直接把消息轉發給其餘對象,第三階段是對第二階段的擴充,能夠實現屢次轉發,轉發給多個對象等。這也許就是設計這三個階段的意義。

另外,一個對象經過消息轉發來響應一條消息,「看起來像」繼承了在其餘類定義的方法實現,這就變相實現了多繼承。

固然,也許多繼承自己就不該該存在。你應該遵循「單一職責」、「高內聚,低耦合」等面向對象設計原則,合理設計類的功能。

參考資料:

  1. Objective-C Runtime Programming Guide

  2. Effective Objective-C 2.0

  3. Objective-C 消息發送與轉發機制原理

  4. NSInvocation

相關文章
相關標籤/搜索