美文网首页RuntimeSwift CafeiOS Developer
揭秘 Objective-C Runtime - 消息转发

揭秘 Objective-C Runtime - 消息转发

作者: SwiftCafe | 来源:发表于2016-11-14 23:23 被阅读183次

    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

    相关文章

      网友评论

        本文标题:揭秘 Objective-C Runtime - 消息转发

        本文链接:https://www.haomeiwen.com/subject/mjjkpttx.html