一、简介:
消息转发是OC底层一种功能强大的实现,为OC方法的调用增加更多的表现力和容错能力。什么是消息转发?简单来说,就是在OC在调用方法但不能找到方法对应实现时,执行的一种补救措施,从而将方法的执行引导到其他地方,为程序执行提供更多可能.
二、先来了解什么是消息发送
消息发送的官方定义:
OC的方法本质:
OC方法的底层实现就是objc_msgSend()
objc_msgSend()前面两个参数:self
和SEL
。对self
的理解一般认为它是对象本身,官方文档的解释是指向接收此消息的对象的指针,其实也不难理解,按照runtime的逻辑,方法的执行先要查找到本类的方法列表,然后执行,因此就需要知道本类是谁。对于_cmd(它保存了正在发送的消息的选择器)是第二个隐式参数,对应方法的实现。总之,self指向对象本身,_cmd指向方法本身。
OC中方法分为类方法和对象方法,对应的调用方式就是:
1.类名调用类方法:
// 对象实例调用
Person *person = [[Person alloc] init];
[person run];
2.对象实例调用实例方法:
// 类方法调用
[Person walk];
OC 函数调用的语法都会被翻译成一个 C 的函数调用 objc_msgSend()
我们用对象调用方法来举例子说明
1.先自定义一个Person类:
2.分别用person对象和消息发送来调用run方法:
3.查看打印结果:
4.可以看到打印了三次,说明方法被调用了三次.其中对象调用一次, objc_msgSend()调用了两次. objc_msgSend()就是方法调用的底层实现.
说明:
可以看到上面写了两种方式的objc_msgSend()调用,这是是LLVM的配置选项,可以选择关闭objc_msgSend的编写检查.具体操作如图:
5.消息发送的具体细节实现会另外写一篇文章.
二、消息转发...
当没有方法的实现,程序会在运行时挂掉并抛出 unrecognized selector sent to …
的异常。但在异常抛出前,Objective-C 的运行时会给你三次拯救程序的机会:
- 动态方法解析: Method Resolution
- 快速转发: Fast Rorwarding
- 完整消息转发: Normal Forwarding
系统在处理消息转发的时候,是按照上面的顺序进行转发的,转发成功则会跳过后面的方法.
1.动态方法解析: Method Resolution
首先,当调用没有实现的方法的时候,Objective-C 运行时会调用 + (BOOL)resolveInstanceMethod:或者 + (BOOL)resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数并返回 YES, 那运行时系统就会重新启动一次消息发送的过程。
这里
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
方法转发.
3. 完整消息转发: Normal Forwarding
与上面不同,可以理解成完整消息转发,用来代替快速转发做更多的事。
methodSignatureForSelector
用来生成方法签名,这个签名就是给 forwardInvocation
中的参数 NSInvocation
调用的。通过NSInvocation
中的SEL来执行下面的转发逻辑.
-
NSInvocation 的内部结构:
1.methodSignatureForSelector这个方法中,如果没有找到方法对应的实现,就会返回一个空的方法签名,最终NSObject找不到SEL。系统就会报开头我们提到的
unrecognized selector sent to instance
错误,最终导致程序报错崩溃。
2.所以我们需要做的是自己新建方法签名,再在forwardInvocation中用你要转发的那个对象调用这个对应的签名,这样也实现了消息转发。
上图中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