美文网首页
iOS的消息转发机制

iOS的消息转发机制

作者: 司空123 | 来源:发表于2019-01-24 18:10 被阅读0次

    一、简介:

    消息转发是OC底层一种功能强大的实现,为OC方法的调用增加更多的表现力和容错能力。什么是消息转发?简单来说,就是在OC在调用方法但不能找到方法对应实现时,执行的一种补救措施,从而将方法的执行引导到其他地方,为程序执行提供更多可能.

    二、先来了解什么是消息发送

    消息发送的官方定义:

    屏幕快照 2019-01-25 上午11.24.37.png

    官方文档

    OC的方法本质:
    OC方法的底层实现就是objc_msgSend()
    objc_msgSend()前面两个参数:selfSEL。对self的理解一般认为它是对象本身,官方文档的解释是指向接收此消息的对象的指针,其实也不难理解,按照runtime的逻辑,方法的执行先要查找到本类的方法列表,然后执行,因此就需要知道本类是谁。对于_cmd(它保存了正在发送的消息的选择器)是第二个隐式参数,对应方法的实现。总之,self指向对象本身,_cmd指向方法本身。

    OC中方法分为类方法和对象方法,对应的调用方式就是:
    1.类名调用类方法:

    // 对象实例调用
    Person *person = [[Person alloc] init];
    [person run];
    

    2.对象实例调用实例方法:

    // 类方法调用
    [Person walk];
    

    OC 函数调用的语法都会被翻译成一个 C 的函数调用 objc_msgSend()
    我们用对象调用方法来举例子说明

    1.先自定义一个Person类:


    屏幕快照 2019-01-24 下午4.54.05.png

    2.分别用person对象和消息发送来调用run方法:


    屏幕快照 2019-01-24 下午4.53.10.png
    屏幕快照 2019-01-24 下午5.09.46.png

    3.查看打印结果:


    屏幕快照 2019-01-24 下午4.53.27.png 屏幕快照 2019-01-24 下午5.10.08.png

    4.可以看到打印了三次,说明方法被调用了三次.其中对象调用一次, objc_msgSend()调用了两次. objc_msgSend()就是方法调用的底层实现.

    说明:
    可以看到上面写了两种方式的objc_msgSend()调用,这是是LLVM的配置选项,可以选择关闭objc_msgSend的编写检查.具体操作如图:

    屏幕快照 2019-01-24 下午5.08.20.png

    5.消息发送的具体细节实现会另外写一篇文章.

    二、消息转发...

    当没有方法的实现,程序会在运行时挂掉并抛出 unrecognized selector sent to …的异常。但在异常抛出前,Objective-C 的运行时会给你三次拯救程序的机会:

    • 动态方法解析: Method Resolution
    • 快速转发: Fast Rorwarding
    • 完整消息转发: Normal Forwarding

    系统在处理消息转发的时候,是按照上面的顺序进行转发的,转发成功则会跳过后面的方法.

    1.动态方法解析: Method Resolution

    首先,当调用没有实现的方法的时候,Objective-C 运行时会调用 + (BOOL)resolveInstanceMethod:或者 + (BOOL)resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数并返回 YES, 那运行时系统就会重新启动一次消息发送的过程。

    屏幕快照 2019-01-24 下午5.48.47.png

    这里
    v 代表函数返回类型void,
    @ 代表self的类型id,
    : 代表_cmd的类型SEL。

    2.快速转发: Fast Rorwarding

    • Method Resolution 不同,Fast Rorwarding 这是一种快速消息转发:只需要在- (id)forwardingTargetForSelector:(SEL)aSelector 方法里面返回一个新对象即可。相当于替换了消息的接收者,进而去新的接收者那里去寻找对应的实现。
    • 通过- (id)forwardingTargetForSelector:(SEL)aSelector方法。如果此方法返回的是新的消息接收对象,则会向新对象转发此消息,如果此方法返回的是 nil 或者self,则会进入系统消息转发机制。具体为向 - (void)forwardInvocation:(NSInvocation *)invocation方法转发.
    屏幕快照 2019-01-24 下午5.58.52.png

    3. 完整消息转发: Normal Forwarding

    与上面不同,可以理解成完整消息转发,用来代替快速转发做更多的事。

    屏幕快照 2019-01-25 上午9.46.06.png

    methodSignatureForSelector用来生成方法签名,这个签名就是给 forwardInvocation中的参数 NSInvocation调用的。通过NSInvocation中的SEL来执行下面的转发逻辑.

    • NSInvocation 的内部结构:


      屏幕快照 2019-01-25 上午10.18.15.png

    1.methodSignatureForSelector这个方法中,如果没有找到方法对应的实现,就会返回一个空的方法签名,最终NSObject找不到SEL。系统就会报开头我们提到的 unrecognized selector sent to instance错误,最终导致程序报错崩溃。
    2.所以我们需要做的是自己新建方法签名,再在forwardInvocation中用你要转发的那个对象调用这个对应的签名,这样也实现了消息转发。

    屏幕快照 2019-01-25 上午10.24.23.png

    上图中methodSignature为nil,导致-[NSObject(NSObject) doesNotRecognizeSelector:] 报错,引起程序崩溃.

    三、总结

    OC方法的调用通过消息发送的形式实现,当方法的实现找不到的情况下,运行时环境会依次进行下面三个阶段的查找:

    第一阶段:
    • (BOOL)resolveInstanceMethod:(SEL)name(实例方法)
    • (BOOL)resolveClassMethod:(SEL)name(类方法)
    第二阶段:
    • (id)forwardingTargetForSelector:(SEL)aSelector(快速转发)

    在此方法中另外返回一个类的对象,该类含有对应方法的实现,runtime会在新类的方法列表中进行查找,找到就去执行,找不到依然会报错.

    第三阶段:
    • (void)forwardInvocation:(NSInvocation *)invocation
    • (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

    methodSignatureForSelector中实现方法签名, forwardInvocation中根据methodSignatureForSelector返回的方法签名进行消息的转发.

    参考链接:
    https://www.jianshu.com/p/2fd4b930588e
    https://www.jianshu.com/p/1bde36ad9938

    相关文章

      网友评论

          本文标题:iOS的消息转发机制

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