為什么Objective-C的消息轉發(fā)要設計三個階段?

我們知道,在Objective-C中如果給一個對象發(fā)送一條它無法處理的消息,就會進入下圖描述的消息轉發(fā)(Message Forwarding)流程,但是為什么要設計這么復雜的流程呢?

消息轉發(fā)流程

消息轉發(fā)可以分為三個階段,不同資料中每個階段的名稱不太一樣,蘋果的官方文檔也沒有明確指出這三個階段,所以這里階段的名稱僅供參考。

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

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

有些情況下,你希望能夠為一個方法動態(tài)地提供實現(xiàn)。例如,Objective-C中可以將一個屬性聲明為@dynamic

@dynamic propertyName;

這樣你就告訴編譯器,與這個屬性相關聯(lián)的setter和getter方法會被動態(tài)添加。編譯器就不會自動為你創(chuàng)建setter和getter以及對應的成員變量(instance variable或叫Ivar)。

你可以通過實現(xiàn)方法resolveInstanceMethod:resolveClassMethod:為指定的selector動態(tài)添加實現(xiàn)。

一個 Objective-C方法不過是一個C函數(shù),這個函數(shù)最少有兩個參數(shù)——self和_cmd。你可以通過class_addMethod函數(shù)把一個函數(shù)添加到一個類中。你需要提供類似下面的函數(shù):

void dynamicMethodIMP(id self, SEL _cmd) {
    // implementation ....
}

在消息轉發(fā)的流程中,使用resolveInstanceMethod:動態(tài)地將一個函數(shù)添加為一個類的方法:

@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最后一個參數(shù)叫做types,是一個描述方法的參數(shù)類型的字符串。

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

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

這個階段的意義是為一個類動態(tài)提供方法實現(xiàn)。嚴格來說,還沒進入消息轉發(fā)流程。respondsToSelector:instancesRespondToSelector:也會調用resolveInstanceMethod:。也就是說,如果resolveInstanceMethod:返回了YES,那么respondsToSelector:instancesRespondToSelector:都會返回YES。

在CoreData中,有些屬性標記為@dynamic,這些屬性的值背后是通過數(shù)據(jù)庫來更新和獲取的,并不需要一個成員變量。所以就會為這些屬性的setter和getter方法實現(xiàn)resolveInstanceMethod:,返回YES,并通過數(shù)據(jù)庫來設置或者獲取該屬性的值。

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

如果第一階段resolveInstanceMethod:返回了NO,就會調用forwardingTargetForSelector:詢問是否把消息轉發(fā)給另一個對象。這相當于把objc_msgSend的第一個參數(shù)改為另一個對象,消息的接收者就改變了。

- (id)forwardingTargetForSelector:(SEL)aSelector {
    return someOtherObject;
}

第三階段:完全消息轉發(fā)機制

如果第二階段的forwardingTargetForSelector:返回了nil,這就進入了所謂完全消息轉發(fā)的機制。

首先調用methodSignatureForSelector:為要轉發(fā)的消息返回正確的簽名:

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

返回了正確的簽名后,就會調用forwardInvocation:將消息轉發(fā)

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

上面代碼是將消息轉發(fā)給其他對象,其實這與第二階段中示例代碼做的事情是一樣的。區(qū)別就在于這個階段會有一個NSInvocation對象。

NSInvocation是一個用來存儲和轉發(fā)消息的對象。它包含了一個Objective-C消息的所有元素:一個target,一個selector,參數(shù)和返回值。每個元素都可以被直接設置。

所以不同與第二階段,在這個階段你可以:

  • 把消息存儲,在你覺得合適的時機轉發(fā)出去,或者不處理這個消息。
  • 修改消息的target,selector,參數(shù)等
  • 多次轉發(fā)這個消息,轉發(fā)給多個對象

顯然在這個階段,你可以對一個OC消息做更多的事情。

總結

第一階段意義在于動態(tài)添加方法實現(xiàn),第二階段直接把消息轉發(fā)給其他對象,第三階段是對第二階段的擴充,可以實現(xiàn)多次轉發(fā),轉發(fā)給多個對象等。這也許就是設計這三個階段的意義。

另外,一個對象通過消息轉發(fā)來響應一條消息,“看起來像”繼承了在其他類定義的方法實現(xiàn),這就變相實現(xiàn)了多繼承。

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

參考資料:

  1. Objective-C Runtime Programming Guide

  2. Effective Objective-C 2.0

  3. Objective-C 消息發(fā)送與轉發(fā)機制原理

  4. NSInvocation

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容