• iOS的消息转发机制详解


    iOS开发过程中,有一类的错误会经常遇到,就是找不到所调用的方法,当然这类问题比较好解决,给当前对象或其父类对象添加该方法即可,使得编译器在编译时能正确找到该方法;或者,还有另外的方法,由于Objective-C是一门动态语言,我们也可以在运行期再给类添加该方法,一样可以解决该问题,而这就涉及了类的消息转发机制。

    本文就主要来介绍一下iOS系统的消息转发机制,探究一下在调用一个方法时,如果本类中没有该方法时,对象究竟是如何进行消息转发的,来避免程序抛出异常。

    异常现象

    当调用的对象方法不存在,即使经过消息转发也不存在时,就会抛出下面的异常

     -[Teacher playPiano]: unrecognized selector sent to instance 0x6000000114c0
     *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Teacher playPiano]: unrecognized selector sent to instance 0x6000000114c0'
    

    解决办法

    针对上述的异常问题,最简单的方法就是直接在类中添加playPiano方法,或者在其继承树中添加该方法,均可以解决该问题,所以这种方法再次不再赘述,下面介绍一下如何利用消息转发机制解决该问题。

    消息转发是在运行时进行的,大致分为两个阶段:第一阶段是先检查接收者,看是否能通过runtime动态添加一个方法,来处理这个unknown selector的消息;第二阶段就是完整的消息转发机制,首先会先查看有没有其它对象能够处理该消息,如果没有,就把该消息的全部信息封装到NSInvocation对象中,看那个对象能否处理,如果还无法处理,就查看继承树中的类是否能够处理该消息,如果到NSObject之前都无法处理该消息,那么最后就会调用NSObject类的doesNotRecognizeSelector方法来抛出异常,表明调用的方法不存在。

    1.动态方法解析

    对象在收到无法处理的消息时,会调用下面的方法,前者是调用类方法时会调用,后者是调用对象方法时会调用

    // 类方法专用
    + (BOOL)resolveClassMethod:(SEL)sel
    // 对象方法专用
    + (BOOL)resolveInstanceMethod:(SEL)sel
    

    在该方法中,需要给对象所属类动态的添加一个方法,并返回YES,表明可以处理

    + (BOOL)resolveInstanceMethod:(SEL)sel
    {
        NSString *method = NSStringFromSelector(sel);
        if ([@"playPiano" isEqualToString:method]) {
            /**
             添加方法
             
             @param self 调用该方法的对象
             @param sel 选择子
             @param IMP 新添加的方法,是c语言实现的
             @param 新添加的方法的类型,包含函数的返回值以及参数内容类型,eg:void xxx(NSString *name, int size),类型为:v@i
             */
            class_addMethod(self, sel, (IMP)playPiano, "v");
            return YES;
        }
        return NO;
    }
    

    2.备援接受者

    经历了第一步后,如果该消息还是无法处理,那么就会调用下面的方法,查询是否有其它对象能够处理该消息

    - (id)forwardingTargetForSelector:(SEL)aSelector
    

    在这个方法里,我们需要返回一个能够处理该消息的对象

    - (id)forwardingTargetForSelector:(SEL)aSelector
    {
        NSString *seletorString = NSStringFromSelector(aSelector);
        if ([@"playPiano" isEqualToString:seletorString]) {
            Student *s = [[Student alloc] init];
            return s;
        }
        // 继续转发
        return [super forwardingTargetForSelector:aSelector];
    }
    

    3.完整的消息转发

    经历了前两步,还是无法处理消息,那么就会做最后的尝试,先调用methodSignatureForSelector:获取方法签名,然后再调用forwardInvocation:进行处理,这一步的处理可以直接转发给其它对象,即和第二步的效果等效,但是很少有人这么干,因为消息处理越靠后,就表示处理消息的成本越大,性能的开销就越大。所以,在这种方式下,会改变消息内容,比如增加参数,改变选择子等等。

    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    - (void)forwardInvocation:(NSInvocation *)anInvocation
    

    下面是改变选择子的例子,比如我们直接调用的是playPiano方法,最后转发给了traval:方法,完整实例参考:MsgSendDemo

    // 完整的消息转发
    - (void)travel:(NSString*)city
    {
        NSLog(@"Teacher travel:%@", city);
    }
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    {
        NSString *method = NSStringFromSelector(aSelector);
        if ([@"playPiano" isEqualToString:method]) {
            
            NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:@"];
            return signature;
        }
        return nil;
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation
    {
        SEL sel = @selector(travel:);
        NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:@"];
        anInvocation = [NSInvocation invocationWithMethodSignature:signature];
        [anInvocation setTarget:self];
        [anInvocation setSelector:@selector(travel:)];
        NSString *city = @"北京";
        // 消息的第一个参数是self,第二个参数是选择子,所以"北京"是第三个参数
        [anInvocation setArgument:&city atIndex:2];
        
        if ([self respondsToSelector:sel]) {
            [anInvocation invokeWithTarget:self];
            return;
        } else {
            Student *s = [[Student alloc] init];
            if ([s respondsToSelector:sel]) {
                [anInvocation invokeWithTarget:s];
                return;
            }
        }
        
        // 从继承树中查找
        [super forwardInvocation:anInvocation];
    }
    

    iOS的消息转发机制给我们提供了更多的选择,来保证消息的正常传递,而了解这些具体的实现方法,则可以让我们的程序更加的健壮。

    参考资料

    iOS消息转发机制详解

    iOS 消息转发机制(VN的逃生之路)

    NSMethodSignature和NSInvocation的用法

    使用NSMethodSignature和NSInvocation实现消息转发

  • 相关阅读:
    MySql 获取当前节点及递归所有上级节点
    MySql创建树结构递归查询存储过程
    F2工作流引擎Web层全新扁平化UI上线
    F2工作流引擎参与者类型成员的交、并、互拆计算规则
    F2工作流引擎之组织用户模型(四)
    F2工作流引擎之 工作流运转模型(三)
    F2工作流引擎之 概述(一)
    离线安装docker,并导入docker镜像
    sudo: /usr/bin/sudo must be owned by uid 0 and have the setuid bit set 的解决办法
    yml 文件中使用环境变量
  • 原文地址:https://www.cnblogs.com/fishbay/p/7216197.html
Copyright © 2020-2023  润新知