消息转发
前文介绍了进行一次发送消息会在相关的类对象中搜索方法列表,如果找不到则会沿着继承树向上一直搜索直到继承树根部(通常为NSObject
),如果还是找不到并且消息转发都失败了就回执行 doesNotRecognizeSelector:
方法报 unrecognized selector
错。
但在抛出异常之前,还有 三次机会 按以下顺序让你拯救程序:
- Method Resolution 动态方法解析
- Fast Forwarding 备用接收者
- Normal Forwarding 完整消息转发
消息转发流程简图:
动态方法解析 Method Resolution
首先,OC运行时会调用 +resolveInstanceMethod:
或者 +resolveClassMethod:
,让你有机会提供一个函数实现。如果你添加了函数并返回YES
, 那运行时系统就会重新启动一次消息发送的过程。
实现一个动态方法解析的例子如下:
1 | @interface Message : NSObject |
如果创建Message
对象并调用sendMessage:
方法:
1 | Message *message = [Message new]; |
控制台会打印以下信息:
1 | normal way : send message = App Dev Club |
但现在我将原来sendMessage:
方法实现给注释掉,覆盖resolveInstanceMethod:
方法:
1 |
|
控制台就会打印以下信息:
1 | method resolution way : send message = App Dev Club |
注意到上面代码有这样一个字符串"v@*"
("v@:"
),它表示方法的参数和返回值,详情参考 Type Encodings
可以看到虽然没有实现sendMessage:
这个函数,但是我们通过class_addMethod
动态添加函数,并执行这个函数的IMP
。从结果看,成功实现了。
如果resolve
方法返回 NO
,运行时就会移到下一步:forwardingTargetForSelector
。
备用接收者 Fast Forwarding
如果目标对象实现- forwardingTargetForSelector:
方法,系统就会在运行时调用这个方法,只要这个方法返回的不是nil
或self
,也会重启消息发送的过程,把这消息转发给其他对象来处理。否则,就会继续Normal Fowarding
。
继续上面Message
类的例子,将sendMessage:
和resolveInstanceMethod:
方法注释掉,然后添加forwardingTargetForSelector:
方法的实现:
1 |
|
此时还缺一个转发消息的类MessageForwarding
,这个类的设计与实现如下:
1 | @interface MessageForwarding : NSObject |
此时,控制台会打印以下信息:
1 | fast forwarding way : send message = App Dev Club |
这里叫Fast,是因为这一步不会创建NSInvocation
对象,但Normal Forwarding
会创建它,所以更快点。
完整消息转发 Normal Forwarding
如果没有使用Fast Forwarding来消息转发,则唯一能做的就是启用完整的消息转发机制了.
首先它会发送-methodSignatureForSelector:
消息获得函数的参数和返回值类型。
如果-methodSignatureForSelector:
返回 nil
,Runtime则会发出 -doesNotRecognizeSelector:
消息,程序这时也就crash掉了,并抛出 unrecognized selector sent to instance 异常信息。
如果返回了一个函数签名,Runtime就会创建一个NSInvocation
对象并发送 -forwardInvocation:
消息给目标对象。
实例:
1 |
|
三种方法的选择
Runtime提供三种方式来将原来的方法实现代替掉,那该怎样选择它们呢?
Method Resolution:由于Method Resolution不能像消息转发那样可以交给其他对象来处理,所以只适用于在原来的类中代替掉。
Fast Forwarding:它可以将消息处理转发给其他对象,使用范围更广,不只是限于原来的对象。
Normal Forwarding:它跟Fast Forwarding一样可以消息转发,但它能通过
NSInvocation
对象获取更多消息发送的信息,例如:target、selector、arguments和返回值等信息。