ios runtime浅析(二):消息转发
如果你给一个对象发送它不认识的消息时,系统会抛出一个错误,但在错误抛出之前,运行时会给改对象发送forwardInvocation:消息,同时传递一个NSInvocation对象作为该消息的参数,NSInvocation对象包封装原始消息和对应的参数。你可以实现forwardInvocation:方法来对不能处理的消息做一些默认的处理,以避免程序崩溃,但正如该函数的名字一样,这个函数主要是用来将消息转发给其它对象。每一个对象都从NSObject类继承了forwardInvocation:方法,但在NSObject中,该方法只是简单的调用doesNotRecognizeSelector:,通过重写该方法你就可以利用forwardInvocation:将消息转发给其它对象。
为了转发一个消息,forwardInvocation:需要做的事:
- 确定消息该往哪发
- 同时发送对应的原始参数
消息可以通过invokeWithTarget:方法发送:
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([someOtherObject respondsToSelector:[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
forwardInvocation:方法可以作为无法认识的消息的分发中心,将它们打包发给不同的接受者,或者作为一个中转站将所有消息发往同一个目的地。它可以将一个消息转换成另外的一个消息,或者只是单纯的把消息“吞下”,不对外响应和抛出错误,forwardInvocation:做什么取决于实现者。
转发和多继承
转发有点像模仿继承,一个对象通过转发响应一个消息,看起来像是继承了其它类实现的方法。
转发
如上图所示,一个Warrior类的实例转发一条negotiate消息到Diplomat类的实例,使得Warrior看起来能够像Diplomat一样进行negotiate。
通过转发消息的对象从而从两个继承体系分支“继承”了方法:原本的继承分支和通过响应消息。在上面的例子中,Warrior看起来好像继承自Diplomat和它的父类。然而转发跟继承有一个重要的区别:继承将不同的功能集合到一个对象;而转发是将不同的功能分配给不同的对象,它将一个问题分解成小的对象,然后通过一种对消息发送者透明的方式把这些对象关联起来。
虽然转发能够模仿继承,但是NSObject类不会因为这两个困惑,类似 respondsToSelector: 和 isKindOfClass: 方法只会查看它的继承体系,不会去查看转发链,比如下面查看Warrior能否响应negotiate消息:
if ( [aWarrior respondsToSelector:@selector(negotiate)] )
...
答案永远是NO,即便它能够通过将该消息转发给Diplomat而响应该消息且不会抛出错误。
同时在调用forwardInvocation:之前,需要先调用methodSignatureForSelector:获取指定selector的方法签名:
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [surrogate methodSignatureForSelector:selector];
}
return signature;
}
示例:
@interface ForwardClass : NSObject
-(void)doSomethingElse;
@end
@implementation ForwardClass
-(void)doSomethingElse
{
NSLog(@"doSomething was called on %@", [self class]);
}
@end
@interface SomeClass : NSObject
{
id forwardClass;
}
-(void)doSomething;
@end
@implementation SomeClass
-(id)init
{
if (self = [super init]) {
forwardClass = [ForwardClass new];
}
return self;
}
-(void)doSomething
{
NSLog(@"doSomething was called on %@", [self class]);
}
-(void)forwardInvocation:(NSInvocation *)invocation
{
if (! forwardClass) {
[self doesNotRecognizeSelector: [invocation selector]];
}
[invocation invokeWithTarget: forwardClass];
}
-(NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
NSMethodSignature *signature = [super methodSignatureForSelector:selector];
if (! signature) {
//生成方法签名
signature = [forwardClass methodSignatureForSelector:selector];
}
return signature;
}
@end
现在给SomeClass对象发送doSomethingElse消息后程序不会crash了:
id someClass = [SomeClass new];
[someClass doSomething];//ForwardTest[1291:56187] doSomething was called on SomeClass
[someClass doSomethingElse];// ForwardTest[1291:56187] doSomething was called on ForwardClass
消息转发有很多的用途,比如:
- 创建一个对象负责把消息转发给一个由其它对象组成的响应链,代理对象会在这个有其它对象组成的集合里寻找能够处理该消息的对象;
- 把一个对象包在一个logger对象里,用来拦截或者纪录一些有趣的消息调用;
- 比如声明类的属性为dynamic,使用自定义的方法来截取和取代由系统自动生成的getter和setter方法。