揭秘 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)]) {

[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 页面中看到, 

另外附上苹果关于消息转发的官方文档,大家也可以参考:



以上是关于揭秘 Objective-C Runtime - 消息转发的主要内容,如果未能解决你的问题,请参考以下文章

Objective-C Runtime

Objective-C runtime初识

Objective-C Runtime 文档翻译—Runtime版本和平台

Objective-C Runtime(转)

Objective-C Runtime 文档翻译—与Runtime的相互作用

Objective-C Runtime 运行时之三:方法与消息(转载)