揭秘 Objective-C Runtime - 消息转发
Posted SwiftCafe
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了揭秘 Objective-C Runtime - 消息转发相关的知识,希望对你有一定的参考价值。
Runtime 消息机制
这次咱们来聊聊 Objective-C Runtime 的消息转发机制。 大家知道, 在 Objc 代码中,我们调用方法实际上在底层都是通过 objc_msgSend()
方法在发消息。 消息是 Objc 的一个核心概念。 咱们之前的一篇文章专门介绍了消息机制 , 可以参看。
我们在 Objective-C 中调用一个方法, 其实就是发送一个消息。 而我们调用的对象来响应这个消息。 比如:
[person sayHello]; |
在实际的 Runtime 中,会被转换成这样一个函数调用:
objc_msgSend(person,@selector(sayHello)) |
objc_msgSend
告诉 person
去响应 @selector(sayHello)
这个消息。 如果 person
中实现了 sayHello 方法, 那么就可以正常响应。 但是如果 person
所引用的实例不能找到 sayHello
的实现,那么在默认的行为下,程序就会崩溃。
消息转发
咱们上面提到的这种崩溃现象, 相信大家多少都会遇到过。 所以在写代码的时候,要尽量避免这样的消息发送导致 App 崩溃。 比如可以使用 respondsToSelector:
方法在调用之前判断这个实例能不能响应这个消息。
if([person respondsToSelector: @selector(sayHello)]) { |
这时一种常见的方法。 当然,在 Runtime 的整个消息传递中,我们还能在其他时机上处理这个事情。 这也就是 Runtime 的消息转发机制。 继承自 NSObject 的类可以覆盖这个方法:
- (void)forwardInvocation:(NSInvocation *)anInvocation |
同时还需要覆盖:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { |
比如我们刚才举得例子中,如果 person
不能响应 sayHello
这个消息,程序并不会马上崩溃, 在这之前 Runtime 会调用 person
的 forwardInvocation:
方法。
forwardInvocation:
接受一个类型为 NSInvocation 的参数。 NSInvocation 中存储了我们发送失败的 sayHello
消息的详细信息。 在这里我们可以把它转发到另外一个实例上面。
通过对 [anInvocation invokeWithTarget:someOtherObject]
的调用, 我们把 sayHello
消息转发到了 someOtherObject 中。 如果 someOtherObject
能够正确的处理这个消息, 那么我们的程序就不会崩溃。
关于消息发送和转发的细节, 涉及到 Runtime 的整体消息传递机制,业绩 isa 指针等这些概念,这里我们不过多展开。
这时候我们再调用 [person sayHello]
之后, 即使 person
本身不能处理这个消息, 程序也不会崩溃, 而是根据我们的转发规则将消息转到可以处理它的实例中了。
但有一点要注意,通过 forwardInvocation 转发的消息不会进入正常的消息验证逻辑,也就是说即便我们通过 forwardInvocation:
将消息正确的转发了。 但 [person respondsToSelector: @selector(sayHello)] 还是会返回 NO。
除非我们同时也覆盖 person 对 respondsToSelector 的实现:
- (BOOL)respondsToSelector:(SEL)aSelector |
除了可以通过 forwardInvocation:
来处理转发规则, 同样还可以忽略掉出错的消息。
结尾
消息转发输入 Objective-C Runtime 的高级特性,在某些特定状况下,如果大家了解到这个特性,就可以解决很多问题。当然苹果的官方文档中也提到, 这个特性也不要过度使用。
完整的实例代码可以在这个 Gist 页面中看到,
另外附上苹果关于消息转发的官方文档,大家也可以参考:
以上是关于揭秘 Objective-C Runtime - 消息转发的主要内容,如果未能解决你的问题,请参考以下文章
Objective-C Runtime 文档翻译—Runtime版本和平台