美文网首页
OC 消息转发流程分析

OC 消息转发流程分析

作者: Onego | 来源:发表于2020-02-01 22:48 被阅读0次

    上一篇消息查找流程 探索了消息查找流程:快速查找慢速查找以及动态方法解析
    但消息机制还不完整,如果动态方法解析之后,仍无法找到IMP,那又该如何处理呢?

    消息转发

        imp = (IMP)_objc_msgForward_impcache;
        cache_fill(cls, sel, imp, inst);
    
    • 没有找到实现, 而且动态方法解析也没有帮助,就会进入消息转发流程

    消息转发实现 __objc_msgForward_impcache

    __objc_msgForward_impcache是通过汇编实现,这里以 arm64为例。

        STATIC_ENTRY __objc_msgForward_impcache
    
        // No stret specialization.
        b   __objc_msgForward
    
        END_ENTRY __objc_msgForward_impcache
    
        ENTRY __objc_msgForward
    
        adrp    x17, __objc_forward_handler@PAGE
        ldr p17, [x17, __objc_forward_handler@PAGEOFF]
        TailCallFunctionPointer x17
        
        END_ENTRY __objc_msgForward
    
    • __objc_msgForward_impcachearm64下,只调用了__objc_msgForward
    • __objc_msgForward 调用了__objc_forward_handler

    _objc_forward_handler

    • _objc_forward_handlerC源码中有默认实现
    // Default forward handler halts the process.
    __attribute__((noreturn)) void 
    objc_defaultForwardHandler(id self, SEL sel)
    {
        _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
                    "(no message forward handler is installed)", 
                    class_isMetaClass(object_getClass(self)) ? '+' : '-', 
                    object_getClassName(self), sel_getName(sel), self);
    }
    void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
    
    • 这里仅作为参考,实际似乎并没有走这里
    • 这就说明_objc_forward_handler在某个地方被修改了
    • 在源码中全局搜索_objc_forward_handler发现了 objc_setForwardHandler()函数

    objc_setForwardHandler

    objc_setForwardHandler
    • 打了个断点发现函数objc_setForwardHandlerdyld初始化镜像::doImageInit的时候触发的具体实现应该放在CoreFoundation__CFInitialize中,但__CFInitialize的并没有找到关于forwardHeadler的实现(此路不通)。
    • 换一种探索方式,既然分歧点从_objc_msgForward_impcache开始,那就给_objc_msgForward_impcache下一个符号断点来窥探一二。

    下符号断点来窥探闭源部分的内容

    1. 给_objc_msgForward_impcache下符号断点

    这里有一点要说明一下:测试Demo是在x86_64架构下运行的,和arm64有一点差异
    比如:在arm64架构下只有_objc_msgForwrd,没有_objc_msgForward_stret

    _objc_msgForward_impcache 断点

    进来了,激动
    接着往下

    2. _objc_msgForward
    _objc_msgForward
    出现_objc_forward_handler和前面探索吻合,继续往下走
    3 .forwarding_prep_0_
    __forwarding_prep_0___
    走到第22行,进入___forwarding___
    __forwarding_prep_0___line_22
    4. forwarding
    ___forwarding___
    第51行出现了forwardingTargetForSelector:,第60行发送该消息
    ___forwarding___line_51

    第93行出现了methodSignatureForSelector:,第104行发送该消息

    ___forwarding___line_93

    第183行出现了forwardInvocation:,第199行发送该消息

    ___forwarding___line_183

    第336行出现了doesNotRecognizeSelector:,第347行发送该消息

    ___forwarding___line_336
    • ___forwarding___中可以得出消息转发的大致流程:
      resolveInstanceMethod: > forwardingTargetForSelector: > methodSignatureForSelector: > forwardInvocation:
    • 汇编跟踪的效率太低,下面介绍一种更快捷的方法

    从OC的消息发送日志入手

    • 之前提到的void instrumentObjcMessageSends(BOOL flag)函数是控制是否记录OC消息发送日志的函数
    • 这个也可以用于非源码环境的工程中,在使用前需要引用一下该函数extern void instrumentObjcMessageSends(BOOL flag);
    1. 写一个Demo
    //引用instrumentObjcMessageSends函数
    extern void instrumentObjcMessageSends(BOOL flag);
    
    @interface Person : NSObject
    - (void)sayInstance;
    + (void)sayClass;
    @end
    @implementation Person 
    /**
    未实现
    - (void)sayInstance;
    + (void)sayClass;
    * 这里会报⚠️,只模拟消息转发的情况
    */
    @end
    
    int main(int argc, const char * argv[]) {
    
        Person *p = [Person new];
        //开启OC消息日志
        instrumentObjcMessageSends(true);
        //发送消息
        [p sayInstance];
        //关闭OC消息日志
        instrumentObjcMessageSends(false);
        return 0;
    }
    
    2. 找到msgSend-***,分析日志
    • /tmp路径下回生成名为msgSend-***
    //摘取了关键的日志信息
    + Person NSObject resolveInstanceMethod:
    + Person NSObject resolveInstanceMethod:
    - Person NSObject forwardingTargetForSelector:
    - Person NSObject forwardingTargetForSelector:
    - Person NSObject methodSignatureForSelector:
    - Person NSObject methodSignatureForSelector:
    - Person NSObject class
    + Person NSObject resolveInstanceMethod:
    + Person NSObject resolveInstanceMethod:
    - Person NSObject doesNotRecognizeSelector:
    - Person NSObject doesNotRecognizeSelector:
    - Person NSObject class
    ......
    
    • 这里出现了几个方法:
    1. resolveInstanceMethod:
      不管是实例方法还是类方法方法动态解析(未处理)一定会调用NSObject resolveInstanceMethod:,也就是说NSObject resolveInstanceMethod:执行失败标志着方法动态解析失败,也是消息转发的必经之路;其中NSObject resolveInstanceMethod之后如果还未得到解决就结束了动态方法解析流程,将进入了消息转发流程
    2. forwardingTargetForSelector:
      瞄一眼forwardingTargetForSelector:苹果官方是如何描述的:
      forwardingTargetForSelector 说明

    总结一下:

    1. 如果要让forwardingTargetForSelector:生效,就需要返回一个对象作为未识别消息接收者,但是返回对象不能是self否则将进入死循环
    2. 如果实现了forwardingTargetForSelector:但是没有适合的对象作为新的接收者,那就让super处理。
    3. 如果未实现forwardingTargetForSelector:就有机会调forwardInvocation:
    1. methodSignatureForSelector:
    methodSignatureForSelector: 说明

    总结一下:

    1. methodSignatureForSelector: 实现并返回方法签名,是否进行转发的先决条件。
    2. 返回了一个方法签名NSMethodSignature *,有了这个方法签名就会创建NSInvocation
    1. doesNotRecognizeSelector:
    • 官方的解释:处理一些接收者无法识别的消息
    • 抛出异常unrecognized selector sent to instance
    1. forwardInvocation:
      forwardingTargetForSelector:文档上提到了这么一个方法有点奇怪,实际上消息日志上并没有记录,不慌先看看forwardInvocation:的官方说明
      forwardInvocation: 说明

    总结一下:

    1. forwardInvocation: 的作用是将消息转发给其它对象
    2. forwardInvocation: 使用前必须实现methodSignatureForSelector:
    3. forwardInvocation: 是从methodSignatureForSelector:中获取需要转发的NSInvocation对象
    3. 验证猜想

    现在Demo的现状运行就会报错

    *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person sayInstance]: unrecognized selector sent to instance 0x10052ddb0'
    

    Person中实现消息转发的方法:

    @implementation Person
    
    - (id)forwardingTargetForSelector:(SEL)aSelector {
        NSLog(@"%s SEL %@",__func__,NSStringFromSelector(aSelector));
        return [super forwardingTargetForSelector:aSelector];
    }
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        NSLog(@"%s SEL %@",__func__,NSStringFromSelector(aSelector));
        if (sel_isEqual(aSelector, @selector(sayInstance))) {
            return [NSMethodSignature signatureWithObjCTypes:"v@:"];
        }
        return [super methodSignatureForSelector:aSelector];
    }
    
    - (void)forwardInvocation:(NSInvocation *)invocation
    {
        SEL aSelector = [invocation selector];
        NSLog(@"%s SEL %@",__func__,NSStringFromSelector(aSelector));
        [super forwardInvocation:invocation];
    }
    
    - (void)doesNotRecognizeSelector:(SEL)aSelector {
        NSLog(@"%s SEL %@",__func__,NSStringFromSelector(aSelector));
        [super doesNotRecognizeSelector:aSelector];
    }
    
    @end
    

    输出

    ***[7058:192271] -[Person forwardingTargetForSelector:] SEL sayInstance
    ***[7058:192271] -[Person methodSignatureForSelector:] SEL sayInstance
    ***[7058:192271] -[Person forwardInvocation:] SEL sayInstance
    ***[7058:192271] -[Person doesNotRecognizeSelector:] SEL sayInstance
    ***[7058:192271] -[Person sayInstance]: unrecognized selector sent to instance 0x1005af3d0
    ***[7058:192271] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person sayInstance]: unrecognized selector sent to instance 0x1005af3d0'
    *** First throw call stack:
    (
        0   CoreFoundation                      0x00007fff385f2d63 __exceptionPreprocess + 250
        1   libobjc.A.dylib                     0x00007fff6e4e1bd4 objc_exception_throw + 48
        2   CoreFoundation                      0x00007fff3867d206 -[NSObject(NSObject) __retain_OA] + 0
        3   forwrdingDemo                       0x0000000100001cb3 -[Person doesNotRecognizeSelector:] + 131
        4   forwrdingDemo                       0x0000000100001c0e -[Person forwardInvocation:] + 174
        5   CoreFoundation                      0x00007fff385992c5 ___forwarding___ + 829
        6   CoreFoundation                      0x00007fff38598ef8 _CF_forwarding_prep_0 + 120
        7   forwrdingDemo                       0x0000000100001d37 main + 71
        8   libdyld.dylib                       0x00007fff6f840405 start + 1
        9   ???                                 0x0000000000000001 0x0 + 1
    )
    libc++abi.dylib: terminating with uncaught exception of type NSException
    
    • 调用顺序
      1.forwardingTargetForSelector:
      2.methodSignatureForSelector:
      3.forwardInvocation:
      4.doesNotRecognizeSelector
    4. 使用forwardingTargetForSelector:将消息转发给指定对象

    写一个新的类Friend实现了 sayInstance方法

    @interface Friend : NSObject
    @end
    
    @implementation Friend
    - (void)sayInstance {
        NSLog(@"%s",__func__);
    }
    @end
    

    PersonforwardingTargetForSelector:方法中,将[Friend new]作为sayInstance的消息接收者

    - (id)forwardingTargetForSelector:(SEL)aSelector {
        NSLog(@"%s SEL %@",__func__,NSStringFromSelector(aSelector));
        if (sel_isEqual(aSelector, @selector(sayInstance))) {
            return [Friend new];
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    

    输出

    ***[7145:202044] -[Person forwardingTargetForSelector:] SEL sayInstance
    ***[7145:202044] -[Friend sayInstance]
    Program ended with exit code: 0
    
    • sayInstance转发成功,Friend执行了sayInstance
    • forwardingTargetForSelector:中指定了Friend处理sayInstance,结束了消息转发流程
    5. 使用forwardInvocation:将消息转发给指定对象

    先将PersonforwardingTargetForSelector:复原

    - (id)forwardingTargetForSelector:(SEL)aSelector {
        NSLog(@"%s SEL %@",__func__,NSStringFromSelector(aSelector));
        return [super forwardingTargetForSelector:aSelector];
    }
    

    在实现methodSignatureForSelector:,对sayInstance进行方法签名

    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    
        NSLog(@"%s SEL %@",__func__,NSStringFromSelector(aSelector));
        if (sel_isEqual(aSelector, @selector(sayInstance))) {
            return [NSMethodSignature signatureWithObjCTypes:"v@:"];
        }
        return [super methodSignatureForSelector:aSelector];
    }
    

    最后实现forwardInvocation:转发消息

    - (void)forwardInvocation:(NSInvocation *)invocation
    {
        SEL aSelector = [invocation selector];
        NSLog(@"%s SEL %@",__func__,NSStringFromSelector(aSelector));
        if ([[Friend new] respondsToSelector:aSelector])
            [invocation invokeWithTarget:[Friend new]];
        else
            [super forwardInvocation:invocation];
    }
    

    输出

    ***[7734:232196] -[Person forwardingTargetForSelector:] SEL sayInstance
    ***[7734:232196] -[Person methodSignatureForSelector:] SEL sayInstance
    ***[7734:232196] -[Person forwardInvocation:] SEL sayInstance
    ***[7734:232196] -[Friend sayInstance]
    Program ended with exit code: 0
    
    • sayInstance转发成功,Friend执行了sayInstance
    • methodSignatureForSelector:中提供了sayInstance创建NSInvocation对象的方法签名
    • forwardInvocation:中做了简单的转发
    • NSInvocation可以根据需求改变入参或者返回参数,这也是forwardInvocation:转发的特点

    附上消息转发的流程图


    消息转发的流程图

    总结

    1. objc_msgSend 从缓存查找imp
    2. 慢速递归查找方法列表
    3. 方法动态解析
      3.1 resolveInstanceMethod: 实例方法解析
      3.2 resolveClassMethod: 类方法解析
      3.3 由于isa指向的独特性,实例方法类方法最终都会掉[NSObject resolveInstanceMethod:]
      3.4 [NSObject resolveInstanceMethod:]方法调用结束,标志着方法动态解析结束
    4. 消息转发
      4.1 forwardingTargetForSelector: 交给其它对象处理
      4.2 methodSignatureForSelector: 提供转发对象NSInvocation需要的方法签名;
      4.3 forwardInvocation: 处理事务(将消息转发给其它对象)
      4.4 forwardingTargetForSelector:forwardInvocation:的区别在于:前者参数和返回值需要和原方法一致,二后者没有这个限制
    5. doesNotRecognizeSelector: 处理接收者无法识别的消息(抛出异常)

    相关文章

      网友评论

          本文标题:OC 消息转发流程分析

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