美文网首页
iOS Runtime之消息转发

iOS Runtime之消息转发

作者: 不会写代码的尬先生 | 来源:发表于2019-05-12 17:56 被阅读0次

接上篇iOS Runtime之消息传递

老规矩,先上经典图


msg.png

消息在继承树上没有找到实现还有消息转发提供三次机会,不至于直接报doesNotRecognizeSelector错误崩溃,当然这也给线上热修复提供了支持。

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self performSelector:@selector(drink)];
}

继承树上没有drink方法,则进入消息转发阶段

一、动态方法解析 resolveInstanceMethod

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if ([NSStringFromSelector(sel) isEqualToString:@"drink"])
    {
        NSLog(@"drink 动态方法解析");
        class_addMethod([self class], sel, (IMP)drinkMethod, nil);
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

void drinkMethod()
{
    NSLog(@"动态方法添加 drink");
}

通过runtime动态添加drinkMethod方法来实现,这个类方法只要动态增加了方法,不管return YES或者NO都不会继续往下走。

二、备用接受者
如果resolveInstanceMethod没有动态添加方法就会继续往下走forwardingTargetForSelector,将消息转发给其他对象来实现。

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if ([NSStringFromSelector(aSelector) isEqualToString:@"drink"])
    {
        NSLog(@"备用接收者");
        return [Son new];
    }
    return [super forwardingTargetForSelector:aSelector];
}

如果Son类中有drink方法,则会执行,如果没有drink则会报doesNotRecognizeSelector崩溃。如果forwardingTargetForSelector方法返回nil或者没有实现,则会进入最后一步完整消息转发。

3、完整消息转发
如果前两步都未处理,则会进入最后的机会,先调用methodSignatureForSelector获取方法签名,每个字符都代表一种参数,具体对应表格可参考官方文档方法签名,也可以直接使用类型编码 @encode(type) 获取表示该类型的字符串,而不必硬编码。然后调用forwardInvocation处理,这一步的处理可以直接转发给其它对象,即和第二步的效果一样,没有必要,故在这一步,会改变消息的内容,比如增加参数,改变方法名称。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if ([NSStringFromSelector(aSelector) isEqualToString:@"drink"])
    {
        NSLog(@"签名 进入forward");
        return [NSMethodSignature signatureWithObjCTypes:"v@:*"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
 SEL sel = @selector(water:);
    NSString *str = [NSString stringWithFormat:@"%s%s%s%s",@encode(void),@encode(id),@encode(SEL),@encode(char *)];
    const char * cStr = [str cStringUsingEncoding:NSUTF8StringEncoding];//等同于"v@:*"
    NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:cStr];
    anInvocation = [NSInvocation invocationWithMethodSignature:signature];
    [anInvocation setTarget:self];
    [anInvocation setSelector:@selector(water:)];
    NSString *water = @"喝水";
    [anInvocation setArgument:&water atIndex:2];//第三个参数是字符串
    
    if ([self respondsToSelector:sel])
    {
        [anInvocation invokeWithTarget:self];
        return;
    }else
    {
        Son *s = [Son new];
        if ([s respondsToSelector:sel])
        {
            [anInvocation invokeWithTarget:s];
            return;
        }
    }
    [super forwardInvocation:anInvocation];
}

- (void)water:(NSString *)str
{
    NSLog(@"完整消息转发 %@",str);
}

通过第三步完整消息转发我们发现drink方法已经转发到了water:方法里,其中"v@:"里,v代表返回值是void,@代表响应者是一个ID对象 ,:代表选择子,代表字符串,我们这里water:方法改变了方法名称,增加了字符串参数。如果还是找不到对应方法,则会沿着继承树查找,最终还是没找到就会报doesNotRecognizeSelector错误程序挂掉。

相关文章

网友评论

      本文标题:iOS Runtime之消息转发

      本文链接:https://www.haomeiwen.com/subject/qictaqtx.html