RuntimeSwift CafeiOS Developer

揭秘 Objective-C Runtime - 消息转发

2016-11-14  本文已影响183人  SwiftCafe

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 会调用 personforwardInvocation: 方法。

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

上一篇 下一篇

猜你喜欢

热点阅读