iOS大牛修炼之路iOS之runtimeiOS模块详解

ios runtime浅析(二):消息转发

2015-06-02  本文已影响6724人  树下老男孩

如果你给一个对象发送它不认识的消息时,系统会抛出一个错误,但在错误抛出之前,运行时会给改对象发送forwardInvocation:消息,同时传递一个NSInvocation对象作为该消息的参数,NSInvocation对象包封装原始消息和对应的参数。你可以实现forwardInvocation:方法来对不能处理的消息做一些默认的处理,以避免程序崩溃,但正如该函数的名字一样,这个函数主要是用来将消息转发给其它对象。每一个对象都从NSObject类继承了forwardInvocation:方法,但在NSObject中,该方法只是简单的调用doesNotRecognizeSelector:,通过重写该方法你就可以利用forwardInvocation:将消息转发给其它对象。
  为了转发一个消息,forwardInvocation:需要做的事:

- (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

消息转发有很多的用途,比如:

上一篇下一篇

猜你喜欢

热点阅读