iOS runtime的两次动态方法决议

2022-08-21  本文已影响0人  水中的蓝天

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

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

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

结论: 当我们没有动态添加方法实现却返回了方法签名时会出发二次动态决议,个人猜测苹果认为你返回了方法签名 那你大概率也会有对应的实现 所以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);
}

上一篇 下一篇

猜你喜欢

热点阅读