iOS底层原理探索-08- Runtime之消息转发

2,192 阅读7分钟

《目录-iOS & OpenGL & OpenGL ES & Metal》
runtime的消息查找阶段已经探索完了,当都不满足条件,会进入消息转发阶段

前言

runtime的消息转发分为3步:

  • 动态方法解析
    • 对象方法解析
    • 类方法解析
  • 快速转发
  • 慢速转发
    • 方法签名
    • 消息转发

一、动态方法解析

根据上篇文章,我们看了lookUpImpOrForward这个方法,在最后都不满足的情况下,会调用resolveMethod_locked这个方法

1、resolveMethod_locked

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();

    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNil(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}

  • 判断
    • cls如果不是元类,说明当前是对象方法,调用resolveInstanceMethod

    • cls如果是元类,说明当前是类方法,调用resolveClassMethod。走完如果没找到,还会走一遍resolveInstanceMethod方法

  • 最后再调用lookUpImpOrForward方法,返回imp

2、对象方法解析resolveInstanceMethod

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);

    if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, resolve_sel, sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveInstanceMethod adds to self a.k.a. cls
    IMP imp = lookUpImpOrNil(inst, sel, cls);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}
  • 容错判断if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) ,是为了看当前类以及父类、元类等有没有实现resolveInstanceMethod:,如果都没有就没必要继续往下走了。(NSObject里面是默认有这个方法的)

  • 如果实现了,就通过objc_msgSend,向当前cls发送消息,也就是调用resolveInstanceMethod:这个已经实现的方法,在这个方法里,我们已经手动给sel添加了一个imp

  • 然后再通过lookUpImpOrNil检查一遍,拿到我们添加的imp

  • 最后返回到lookUpImpOrForward方法,重新循环去找,最终返回imp

2、类方法解析resolveClassMethod

static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());

    if (!lookUpImpOrNil(inst, @selector(resolveClassMethod:), cls)) {
        // Resolver not implemented.
        return;
    }

    Class nonmeta;
    {
        mutex_locker_t lock(runtimeLock);
        nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
        // +initialize path should have realized nonmeta already
        if (!nonmeta->isRealized()) {
            _objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
                        nonmeta->nameForLogging(), nonmeta);
        }
    }
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveClassMethod adds to self->ISA() a.k.a. cls
    IMP imp = lookUpImpOrNil(inst, sel, cls);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}
  • 和上面的容错一样

  • 如果实现了,就通过objc_msgSend,向当前cls的元类 ->发送消息,调用resolveClassMethod方法,给sel添加imp,跟上面流程一样

  • 特别提醒:如果没有实现,还会走一次 对象方法解析 resolveInstanceMethod,是因为有一种特殊情况,就是元类的父类是根元类,根元类的父类是NSObject,所以要在走一次这个方法,看NSObject里面有没有实现。

    • 其实也可以说,所有没有实现的对象方法和类方法,都会跑进NSObject里面的 resolveInstanceMethod方法,但并不意味着 所有的防崩溃方法都要写在这里!原因:
    • 耦合度太高
    • 子类中已经重写了这个方法
    • 一些特殊方法直接被跳转,体验就会很差

代码示例:

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"%s ",__func__);
    
    NSString *name = NSStringFromSelector(sel);
    
    if ([name isEqual:@"sayHello"]) {
    
        //如果是调用sayHello方法,就返回一个sayHappy的IMP
        IMP newIMP = class_getMethodImplementation(self, @selector(sayHappy));
        
        Method newMethod = class_getInstanceMethod(self, @selector(sayHappy));
        
        const char *newType = method_getTypeEncoding(newMethod);
        
        return class_addMethod(self, sel, newIMP, newType);
    }
    
    return [super resolveInstanceMethod:sel];
}

二、快速转发

如果我们继续上面的思路走的话,会发现这个方法已经走完了,线索就断掉了。。。

其实在lookUpImpOrForward里有一个log_and_fill_cache方法,这个方法里面有一个logMessageSend方法调用, 这一系列操作(方法就不一一展示了,太多了,直接到达目的地)中有一步骤是打印日志,而日志存储的位置:

snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid **());**

通过全局探索,发现是否打印的开关是这么一句代码:

instrumentObjcMessageSends(BOOL flag)

于是,我们跑一下代码看一下,(随便创建一份工程,源码跑不动)准备工作:

extern void instrumentObjcMessageSends(BOOL flag);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
  
        LGPerson *objc = [[LGPerson alloc] init];
        //打开开关
        instrumentObjcMessageSends(true);
        //这个实例方法,只声明了,没有实现,跑起来肯定会崩溃
        [objc sayHello];
        //关闭开关
        instrumentObjcMessageSends(false);
        
        
        
    }
    return 0;
} 

我们运行,找一下/tmp/msgSends这个路径存储的日志文件看一下:

从日志中可以看出,动态方法解析之后调用了forwardingTargetForSelector方法。我们去官方看一下这段代码的含义:

大致意思就是,找一个备用的接收者,即返回一个实现了这个方法的对象。

代码示例:

- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s ",__func__);
    
    NSString *name = NSStringFromSelector(aSelector);
    if ([name isEqual:@"sayHello"]) {
    	//Jack这个类 声明并实现了 sayHello这个方法
        return [Jack alloc];
    }
    return [super forwardingTargetForSelector:aSelector];
}

如果没有这个备用接收者,或者没有实现这个方法,就会走到慢速转发

三、慢速转发

在快速转发的官方介绍中,我有圈起来,如果不实现快速转发,会调用forwardInvocation:在这方法,那我们来看一下这个方法的官方介绍:

官方介绍中也圈起来了,要重写forwardInvocation:这个方法,就必须同时重写methodSignatureForSelector:方法,我们继续看一下官方介绍:

慢速流程流程就是先走methodSignatureForSelector提供一个方法签名,然后走forwardInvocation通过NSInvocation来实现消息的转发

代码示例:

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    
    NSLog(@"%s ",__func__);
    
    NSString *name = NSStringFromSelector(aSelector);
    if ([name isEqual:@"sayHello"]) {
        //方法签名
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

//其实这个方法可以空着不写
-(void)forwardInvocation:(NSInvocation *)anInvocation{
    
    NSLog(@"%s ",__func__);
    
    SEL aSelector = [anInvocation selector];

    if ([[Jack alloc] respondsToSelector:aSelector]){
        
        [anInvocation invokeWithTarget:[Jack alloc]];
        
    }else{
        
        [super forwardInvocation:anInvocation];
    }
      
    
}

四、坑点

有这么一个情况,resolveInstanceMethod调用了2次

+ (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__);
    
    NSString *name = NSStringFromSelector(aSelector);
    
    if ([name isEqual:@"sayHello"]) {
        //方法签名  v-返回值,@-传入对象,:-传入sel
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    
    return [super methodSignatureForSelector:aSelector];
}
//通过anInvocation进行消息转发,也可以空着这个方法不作处理
-(void)forwardInvocation:(NSInvocation *)anInvocation{
    
    NSLog(@"%s ",__func__);
  
}

原因: 在调用methodSignatureForSelector方法的时候,我们传了一个type,这时候系统会去进行匹配,走到class_getInstanceMethod这个方法里面:

Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    // This deliberately avoids +initialize because it historically did so.

    // This implementation is a bit weird because it's the only place that 
    // wants a Method instead of an IMP.

#warning fixme build and search caches
        
    // Search method lists, try method resolver, etc.
    lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);

#warning fixme build and search caches

    return _class_getMethod(cls, sel);
}

又会走到lookUpImpOrForward方法,最终还是进入resolveMethod_locked这个方法,然后就会再走一次 resolveInstanceMethod方法。

** 坑点总结**:如果出现走两次resolveInstanceMethod方法的情况,是因为在签名之后,系统做了一些操作:会根据我们传入的签名type进行匹配,调用class_getInstanceMethod方法就会再走一次。

五、总结

前提:整个查找流程没有找到该方法,然后进入消息转发流程

  1. 首先来到动态方法解析,添加一个方法实现(给sel指定一个imp

    • 对象方法,调用+(BOOL)resolveInstanceMethod:(SEL)sel方法
    • 类方法,调用+(BOOL)resolveClassMethod:(SEL)sel方法
  2. 快速转发,如果动态方法解析没有找到对应的处理方法,就会来到这里

    • 调用- (id)forwardingTargetForSelector:(SEL)aSelector方法,return一个备用接收者
  3. 慢速转发,如果快速转发也没有处理,就会来到这里

    • 第一步,调用-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector方法拿到方法签名
    • 第二步,调用-(void)forwardInvocation:(NSInvocation *)anInvocation通过NSInvocation来实现消息转发