美文网首页
iOS runtime的两次动态方法决议

iOS runtime的两次动态方法决议

作者: 水中的蓝天 | 来源:发表于2022-08-21 00:02 被阅读0次

    基本消息机制流程:iOS 底层 - runtime之objc_msgSend

    什么情况下会走两次动态方法决议呢 ?

    以对象方法为例在来到消息动态转发阶段后会执行以下逻辑

    • 未动态解析过:调用+ (BOOL)resolveInstanceMethod:(SEL)sel并添加了方法的实现,解析成功 底层执行 goto retry,标记为已经动态解析 走消息发送流程:从receiverClass的cache中查找方法(lookUpImpOrForward)这一步开始执行

    • 已经动态解析过:直接走消息转发-->_objc_msgForward_impcache,
      调用 - (id)forwardingTargetForSelector:(SEL)aSelector 看是否转发给其他实例来实现, 返回空就调用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector 看是否返回了方法签名, 返回方法签名就再走一次动态方法决议, 结果还是没动态添加方法就会来到forwardInvocation 到此执行结束

    结论: 当我们没有动态添加方法实现却返回了方法签名时会出发二次动态决议,个人猜测苹果认为你返回了方法签名 那你大概率也会有对应的实现 所以forwardInvocation 之前就再次执行了动态决议, 避免在动态决议之后 && 方法签名返回之前你悄悄的给动态添加了方法的实现 比如你直接就在methodSignatureForSelector中动态添加了方法的实现

    代码测试

    
    @interface LPersion : NSObject
    
    - (void)test;
    
    @end
    
    + (BOOL)resolveClassMethod:(SEL)sel {
        NSLog(@"来到--> %s",__func__);
        return [super resolveClassMethod:sel];
    }
    
    + (BOOL)resolveInstanceMethod:(SEL)sel {
        NSLog(@"来到--> %s",__func__);
        return [super resolveInstanceMethod:sel];
    }
    
    - (id)forwardingTargetForSelector:(SEL)aSelector {
        NSLog(@"来到--> %s",__func__);
        return [super forwardingTargetForSelector:aSelector];
    }
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        NSLog(@"来到--> %s",__func__);
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation {
        NSLog(@"来到--> %s",__func__);
    }
    
    打印结果
    来到--> +[LPersion resolveInstanceMethod:]
    来到--> -[LPersion forwardingTargetForSelector:]
    来到--> -[LPersion methodSignatureForSelector:]
    来到--> +[LPersion resolveInstanceMethod:]
    来到--> -[LPersion forwardInvocation:]
    
    

    由于_objc_msgForward_impcache内部的实现没有开源,所以下面用下面方式来探索后续流程
    lookUpImpOrForward--> log_and_fill_cache--> logMessageSend--> instrumentObjcMessageSends

    开启 instrumentObjcMessageSends需要用到extern关键字,这个关键字是可以定义和声明一个外部函数(没有被static修饰的)
    这个日志开启之后,会在 /private/tmp目录下创建一个 msgSends-xxxx文件

    
    bool logMessageSend(bool isClassMethod,
                        const char *objectsClass,
                        const char *implementingClass,
                        SEL selector)
    {
        char    buf[ 1024 ];
        // Create/open the log file
        if (objcMsgLogFD == (-1))
        {
            snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ());
            objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid());
            if (objcMsgLogFD < 0) {
                // no log file - disable logging
                objcMsgLogEnabled = false;
                objcMsgLogFD = -1;
                return true;
            }
        }
    
        // Make the log entry
        snprintf(buf, sizeof(buf), "%c %s %s %s\n",
                isClassMethod ? '+' : '-',
                objectsClass,
                implementingClass,
                sel_getName(selector));
    
        objcMsgLogLock.lock();
        write (objcMsgLogFD, buf, strlen(buf));
        objcMsgLogLock.unlock();
    
        // Tell caller to not cache the method
        return false;
    }
    
    void instrumentObjcMessageSends(BOOL flag)
    {
        bool enable = flag;
    
        // Shortcut NOP
        if (objcMsgLogEnabled == enable)
            return;
    
        // If enabling, flush all method caches so we get some traces
        if (enable)
            _objc_flush_caches(Nil);
    
        // Sync our log file
        if (objcMsgLogFD != -1)
            fsync (objcMsgLogFD);
    
        objcMsgLogEnabled = enable;
    }
    
    

    示例

    extern instrumentObjcMessageSends(BOOL flag);
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        instrumentObjcMessageSends(true);
        [[LPersion new] test];
        instrumentObjcMessageSends(false);
    }
    
    

    相关文章

      网友评论

          本文标题:iOS runtime的两次动态方法决议

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