揭秘 Objective-C Runtime - 消息转发
Objective-C Runtime 是整个 iOS App 的基础环境, 也是各大公司面试常常问起的主题,咱们来聊聊吧。
Runtime 消息机制
这次咱们来聊聊 Objective-C Runtime 的消息转发机制。 大家知道, 在 Objc 代码中,我们调用方法实际上在底层都是通过 objc_msgSend()
方法在发消息。 消息是 Objc 的一个核心概念。 咱们之前的一篇文章专门介绍了消息机制 Objective-C Runtime 消息机制 - 代码背后发生的事情, 可以参看。
我们在 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)]) {
[person sayHello];
}
这时一种常见的方法。 当然,在 Runtime 的整个消息传递中,我们还能在其他时机上处理这个事情。 这也就是 Runtime 的消息转发机制。 继承自 NSObject 的类可以覆盖这个方法:
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([someOtherObject respondsToSelector:
[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
同时还需要覆盖:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature* signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
signature = [someOtherObject methodSignatureForSelector:aSelector];
}
return signature;
}
比如我们刚才举得例子中,如果 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
{
if ( [super respondsToSelector:aSelector] )
return YES;
else {
//对于我们特定转发的 Selector 可以在这里返回 YES
}
return NO;
}
除了可以通过 forwardInvocation:
来处理转发规则, 同样还可以忽略掉出错的消息。
结尾
消息转发输入 Objective-C Runtime 的高级特性,在某些特定状况下,如果大家了解到这个特性,就可以解决很多问题。当然苹果的官方文档中也提到, 这个特性也不要过度使用。
完整的实例代码可以在这个 Gist 页面中看到, https://gist.github.com/swiftcafex/1de52fc40e2cdf07463668258c728979
另外附上苹果关于消息转发的官方文档,大家也可以参考:https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtForwarding.html
更多精彩内容可关注微信公众号:
swift-cafe