咱们知道,在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消息作更多的事情。
第一阶段意义在于动态添加方法实现,第二阶段直接把消息转发给其余对象,第三阶段是对第二阶段的扩充,能够实现屡次转发,转发给多个对象等。这也许就是设计这三个阶段的意义。
另外,一个对象经过消息转发来响应一条消息,“看起来像”继承了在其余类定义的方法实现,这就变相实现了多继承。
固然,也许多继承自己就不该该存在。你应该遵循“单一职责”、“高内聚,低耦合”等面向对象设计原则,合理设计类的功能。