Runtime快速上手(2)消息转发

首先看一张图

runtime2.png

当我们向目标对象发送的消息如[A B]向A对象发送B消息,而无法应答时,一般会报错“unrecognized selector sent to instance..”,而在报错前一般会经过如下几个方法:

+(BOOL)resolveInstanceMethod:(SEL)sel;
-(id)forwardingTargetForSelector:(SEL)aSelector;
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
-(void)forwardInvocation:(NSInvocation *)anInvocation;

这意味着我们在报错前,有机会将错误的消息处理掉而达到我们想要的效果。

一、resolveInstanceMethod

对象无法处理testMyObject消息时会首先调用这个方法,这里我们在ViewController中重写这个方法:

+(BOOL)resolveInstanceMethod:(SEL)sel
{
    NSString *methodName = NSStringFromSelector(sel);
    if ([methodName isEqualToString:@"testMyObject"]) {
        class_addMethod([self class], sel, (IMP)myMethod,"v@:");
        return YES;
    }
    //class_addmethod
    //参数1:给谁添加
    //参数2:添加的selector
    //参数3:添加的imp实现
    //参数4:"v@:"方法的签名,代表没有参数的方法。
  return [super resolveInstanceMethod:sel];
}

void myMethod(id self, SEL _cmd) {
    NSLog(@"我被调用了");
}

然后我们测试下:

-(void)testMethodRepost
{
    NSLog(@"----------测试消息转发----------");
    [self performSelector:@selector(testMyObject)];
}

输出结果:

**2016-03-03 10:52:57.233 FL_RUNTIME_DEMO[12084:4359110] ----------****测试消息转发****----------**
**2016-03-03 10:52:59.928 FL_RUNTIME_DEMO[12084:4359110] ****我被调用了**

resolveInstanceMethod中,我们判断发送的消息是不是“testMyObject”,如果是的,通过class_addMethod向当前对象添加了一个新的方法testMyObject,其中的IMP使用的是void myMethod(id self, SEL _cmd)的实现。

二、forwardingTargetForSelector

在第一步的resolveInstanceMethod中如果我们仍然无法处理问题,则会进入第二步。
我们删除之前的resolveInstanceMethod后,重写这个forwardingTargetForSelector方法:

-(id)forwardingTargetForSelector:(SEL)aSelector
{
    NSString *selectorName = NSStringFromSelector(aSelector);
    if ([selectorName isEqualToString:@"testMyObject"]) {
        myObject *myobject = [[myObject alloc] init];
        return myobject;
    }
    return [super forwardingTargetForSelector:aSelector];
}

其中myObject的实现是:

@interface myObject : NSObject

-(void)testMyObject;

@end

@implementation myObject

-(void)testMyObject
{
    NSLog(@"测试成功");
}

@end

运行结果:

**2016-03-03 11:02:54.230 FL_RUNTIME_DEMO[12145:4367206] ----------****测试消息转发****----------**
**2016-03-03 11:02:56.950 FL_RUNTIME_DEMO[12145:4367206] ****测试成功**

forwardingTargetForSelector中,我们判断这条消息是否是“testMyObject”,如果是,我们返回了一个myObject实例。
这里实际上是对消息指定了一个新的发送对象,将"testMyObject"转发给了这个myObject实例由它来相应这个消息。
这里的返回值就是需要相应这个消息的对象。
由此我们实现了对消息的转发。

三、forwardInvocation

如果在上述两步中我们仍然无法对消息进行处理,则会进入forwardInvocation这个方法中,不过在执行这个方法前会首先调用methodSignatureForSelector来请求一个签名,从而生成一个NSInvocation,对消息进行完全转发。

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
    if (!signature) {
        if([myObject instancesRespondToSelector:aSelector])
        {
            signature = [myObject instanceMethodSignatureForSelector:aSelector];
        }
    }
    return signature;
}

-(void)forwardInvocation:(NSInvocation *)anInvocation
{
    if ([myObject instancesRespondToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:[[myObject alloc] init]];
    }
}

运行结果如下:

**2016-03-03 11:09:50.281 FL_RUNTIME_DEMO[12187:4373641] ----------****测试消息转发****----------**
**2016-03-03 11:09:54.460 FL_RUNTIME_DEMO[12187:4373641] ****测试成功**

推荐阅读更多精彩内容

  • 文中的实验代码我放在了这个项目中。 以下内容是我通过整理[这篇博客] (http://yulingtianxia....
    茗涙阅读 379评论 0 6
  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    萌萌的小伟哥阅读 741评论 0 9
  • 参考链接: http://www.cnblogs.com/ioshe/p/5489086.html 简介 Runt...
    乐乐的简书阅读 1,247评论 0 9
  • 消息转发三部曲: 接上面消息发送,如果当前类和父类中都没有找到实现,那么就会开始尝试动态方法解析。 动态方法解析 ...
    s_在路上阅读 1,590评论 2 14
  • 目录 Objective-C Runtime到底是什么 Objective-C的元素认知 Runtime详解 应用...
    Ryan___阅读 1,004评论 1 3