咱們知道,在Objective-C中若是給一個對象發送一條它沒法處理的消息,就會進入下圖描述的消息轉發(Message Forwarding)流程,可是爲何要設計這麼複雜的流程呢?html
消息轉發能夠分爲三個階段,不一樣資料中每一個階段的名稱不太同樣,蘋果的官方文檔也沒有明確指出這三個階段,因此這裏階段的名稱僅供參考。數據庫
下面咱們就經過詳細解讀每一個階段來回答開篇提出的問題。bash
有些狀況下,你但願可以爲一個方法動態地提供實現。例如,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,參數和返回值。每一個元素均可以被直接設置。
因此不一樣與第二階段,在這個階段你能夠:
顯然在這個階段,你能夠對一個OC消息作更多的事情。
第一階段意義在於動態添加方法實現,第二階段直接把消息轉發給其餘對象,第三階段是對第二階段的擴充,能夠實現屢次轉發,轉發給多個對象等。這也許就是設計這三個階段的意義。
另外,一個對象經過消息轉發來響應一條消息,「看起來像」繼承了在其餘類定義的方法實現,這就變相實現了多繼承。
固然,也許多繼承自己就不該該存在。你應該遵循「單一職責」、「高內聚,低耦合」等面向對象設計原則,合理設計類的功能。