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

消息轉發(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)了多繼承。
當然,也許多繼承本身就不應該存在。你應該遵循“單一職責”、“高內聚,低耦合”等面向對象設計原則,合理設計類的功能。