探索Runtime动态方法解析与消息转发流程

846 阅读10分钟

探索objc_msgSend函数的实现流程一文中,我们最后分析到了 lookUpImpOrForward 函数,该函数会从自己和父类的method_list中遍历寻找objc_msgSend中的SEL所需要的IMP,如果遍历结束后仍然未找到对应的IMP,就会进入动态方法解析流程,动态方法解析还未找见的话,就会进入消息转发流程,今天我们还是从runtime源码来着手分析,动态方法解析和消息转发的流程。

    // No implementation found. Try method resolver once.
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlock();
        _class_resolveMethod(cls, sel, inst);            //开始动态方法解析
        runtimeLock.lock();
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;
    }

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.

    imp = (IMP)_objc_msgForward_impcache;        //消息转发
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlock();

    return imp;

##动态方法解析 我们开始分析动态方法解析前,先要了解一些概念,先来查看一个类的结构:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

再来了解一下isa:isa是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。所以我们可以把类方法看做是其isa指向的元类的实例方法。看一张关系图(isa走位图):

image.png

举个例子说明一下:假设有个Person类继承自NSObject,再有一个Student类继承自Person,那么Student就是图中的SubClass,Person就是图中的SuperClass,NSObject便是其中的RootClass。 需要弄清的有两点:

  1. 所有的元类最终继承一个根元类,根元类isa指针指向本身,形成一个封闭的内循环。
  2. 根元类的父类是根类。 3.NSObject的元类的父类是NSObject,NSObject的isa指针又指向NSObject的元类,所以在NSObject里面的所有方法,NSObject的元类也都拥有,所以如下调用是不会报错的
@interface NSObject (Add)
+(void)hello;
@end
@implementation NSObject (Add)
-(void)hello{
    NSLog(@"hello...");
}
@end

//然后调用
[NSObject hello];

接下来我们进入正题,开始分析动听方法解析(_class_resolveMethod) 的执行流程:

void _class_resolveMethod(Class cls, SEL sel, id inst)
{ 
    if (! cls->isMetaClass()) {      //如果cls不是一个元类,说明调用的是一个实例方法
        //此时 cls表示的是类,而inst代表的是类的实例
        _class_resolveInstanceMethod(cls, sel, inst);
    } 
    else {
        //此时 cls表示的是元类,而inst代表的是类
        _class_resolveClassMethod(cls, sel, inst);
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}
  1. 实例方法解析
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
     //cls->ISA() 此时指向的是元类,判断元类是否实现了resolveInstanceMethod方法(相当于该类判断是否有resolveInstanceMethod的类方法)
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    //如果找到了 就重启消息发送流程
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, SEL_resolveInstanceMethod, 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(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);

    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));
        }
    }
}

IMP lookUpImpOrNil(Class cls, SEL sel, id inst, 
                   bool initialize, bool cache, bool resolver)
{
    IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
    if (imp == _objc_msgForward_impcache) return nil;
    else return imp;
}

可以看到,又进入了 lookUpImpOrForward 方法去查找IMP,我们知道lookUpImpOrForward方法里如果没有递归查找到IMP就又会触发动态方法解析和消息转发,这样不是陷入死循环了吗?其实并不是这样,我们按执行顺序开始走一遍,先去元类里查找,然后再去元类的父类也就是根源类查找,都没有找到,再去根源类的父类也就是NSObject查找,我们查看NSObject的源码:

image.png
发现NSObject是实现了resolveInstanceMethod这个方法,只是返回了NO。系统就是通过这样的方式来避免产生死递归。 看源码可以看到,如果找到了resolveInstanceMethod方法,系统就会重新执行一遍 objc_msgSend 的流程,去回调这个方法

  1. 类方法解析

看源码

_class_resolveClassMethod(cls, sel, inst);
  if (!lookUpImpOrNil(cls, sel, inst, 
         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        _class_resolveInstanceMethod(cls, sel, inst);
    }

1、_class_resolveClassMethod:开始从元类里查找resolveClassMethod方法,一直递归去查找,这里如果找到了,依然会重启消息发送方法

static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
//此时的cls是元类,inst是类
    assert(cls->isMetaClass());

    if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

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

注意:跟实例方法不同的是:类方法重启objc_msgSend时传入的参数是_class_getNonMetaClass(cls, inst),而实例方法重启时传的参数是cls(类对象),下面我们来分析一下_class_getNonMetaClass的实现:

Class _class_getNonMetaClass(Class cls, id obj)
{
    mutex_locker_t lock(runtimeLock);
    cls = getNonMetaClass(cls, obj);
    assert(cls->isRealized());
    return cls;
}
static Class getNonMetaClass(Class metacls, id inst)
{
    static int total, named, secondary, sharedcache;
    runtimeLock.assertLocked();

    realizeClass(metacls);

    total++;

    //metacls是元类  inst是类
    // 如果metacls不是元类了,说明此时传进来的inst类是NSObject,就直接返回元类
    if (!metacls->isMetaClass()) return metacls;

    // metacls really is a metaclass

    // special case for root metaclass
    // where inst == inst->ISA() == metacls is possible
    //如果元类的isa指向了自己,说明此时元类是根元类  ,返回它的父类 即NSObject
    if (metacls->ISA() == metacls) {
        Class cls = metacls->superclass;
        assert(cls->isRealized());
        assert(!cls->isMetaClass());
        assert(cls->ISA() == metacls);
        if (cls->ISA() == metacls) return cls;
    }

    //一般情况
    if (inst) {
        Class cls = (Class)inst;
        realizeClass(cls);
        // cls may be a subclass - find the real class for metacls
        while (cls  &&  cls->ISA() != metacls) {
//这里主要判断isa是否被篡改 — 异常判断
            cls = cls->superclass;
            realizeClass(cls);
        }
        if (cls) {
            assert(!cls->isMetaClass());
            assert(cls->ISA() == metacls);
//直接返回cls 类对象
            return cls;
        }
#if DEBUG
        _objc_fatal("cls is not an instance of metacls");
#else
        // release build: be forgiving and fall through to slow lookups
#endif
    }

    // try name lookup
    {
        Class cls = getClass(metacls->mangledName());
        if (cls->ISA() == metacls) {
            named++;
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: %d/%d (%g%%) "
                             "successful by-name metaclass lookups",
                             named, total, named*100.0/total);
            }

            realizeClass(cls);
            return cls;
        }
    }

    // try secondary table
    {
        Class cls = (Class)NXMapGet(nonMetaClasses(), metacls);
        if (cls) {
            secondary++;
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: %d/%d (%g%%) "
                             "successful secondary metaclass lookups",
                             secondary, total, secondary*100.0/total);
            }

            assert(cls->ISA() == metacls);            
            realizeClass(cls);
            return cls;
        }
    }

    // try any duplicates in the dyld shared cache
    {
        Class cls = nil;

        int count;
        Class *classes = copyPreoptimizedClasses(metacls->mangledName(),&count);
        if (classes) {
            for (int i = 0; i < count; i++) {
                if (classes[i]->ISA() == metacls) {
                    cls = classes[i];
                    break;
                }
            }
            free(classes);
        }

        if (cls) {
            sharedcache++;
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: %d/%d (%g%%) "
                             "successful shared cache metaclass lookups",
                             sharedcache, total, sharedcache*100.0/total);
            }

            realizeClass(cls);
            return cls;
        }
    }

    _objc_fatal("no class for metaclass %p", (void*)metacls);
}

可以看到,_class_getNonMetaClass的正常流程还是返回了类对象,为什么要返回类对象呢,我们正常的思路是谁出问题,在谁那里修复(应该在元类里取修复),但是类方法是在元类中的,元类是一个虚拟的类,没办法进行书写,所以苹果就设计了这么一个方式,直接返回类,在类里面去实现resolveClassMethod

2、但是我们看类方法的_class_resolveMethod里发现,_class_resolveClassMethod执行完了如果没有找到的话,还有一步操作_class_resolveInstanceMethod(与上面实例方法步骤一致),该方法会继续从元类里寻找resolveInstanceMethod方法,直到NSObject,这也证明了我们前面的NSObject (Add)类的代码运行不会报错。

综上所述,我们可以在NSObject扩展里实现resolveInstanceMethod方法,就可以拦截所有的未实现方法。

消息转发

如果动态方法解析没有进行任何处理,那么就会进入消息转发流程:

imp = (IMP)_objc_msgForward_impcache;

	STATIC_ENTRY __objc_msgForward_impcache
	// Method cache version

	// THIS IS NOT A CALLABLE C FUNCTION
	// Out-of-band Z is 0 (EQ) for normal, 1 (NE) for stret

	beq	__objc_msgForward
	b	__objc_msgForward_stret
	
	END_ENTRY __objc_msgForward_impcache

发现此处被苹果闭源了,那么就没有办法了吗??? 我们写一个测试代码

2019-03-02 17:15:45.456611+0800 CrashDemo[42591:4806004] -[ViewController hello]: unrecognized selector sent to instance 0x7fe4a6000000
2019-03-02 17:15:45.464384+0800 CrashDemo[42591:4806004] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[ViewController hello]: unrecognized selector sent to instance 0x7fe4a6000000'
*** First throw call stack:
(
	0   CoreFoundation                      0x00000001096301bb __exceptionPreprocess + 331
	1   libobjc.A.dylib                     0x00000001086f3735 objc_exception_throw + 48
	2   CoreFoundation                      0x000000010964ef44 -[NSObject(NSObject) doesNotRecognizeSelector:] + 132
	3   UIKitCore                           0x000000010bfeab4a -[UIResponder doesNotRecognizeSelector:] + 287
	4   CoreFoundation                      0x0000000109634ed6 ___forwarding___ + 1446
	5   CoreFoundation                      0x0000000109636da8 _CF_forwarding_prep_0 + 120
	6   CrashDemo                           0x0000000107dd67f0 main + 80
	7   libdyld.dylib                       0x000000010aac9575 start + 1
	8   ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

查看堆栈信息可以看出是在CoreFoundation下执行了_forwarding_prep_0、__forwarding__等方法,所以我们来尝试反编译CoreFoundation的可执行文件来尝试一下是否可以找到一些信息:

image.png
还真的被我们找到了_forwarding_prep_0这个信息,点进去:

int ___forwarding_prep_0___(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
    *(rsp + 0xa0) = zero_extend_64(xmm7);
    *(rsp + 0x90) = zero_extend_64(xmm6);
    *(rsp + 0x80) = zero_extend_64(xmm5);
    *(rsp + 0x70) = zero_extend_64(xmm4);
    *(rsp + 0x60) = zero_extend_64(xmm3);
    *(rsp + 0x50) = zero_extend_64(xmm2);
    *(rsp + 0x40) = zero_extend_64(xmm1);
    *(rsp + 0x30) = zero_extend_64(xmm0);
    stack[2021] = arg0;
    rax = ____forwarding___(rsp, 0x0);
    if (rax != 0x0) {
            rax = *rax;
    }
    else {
            rax = objc_msgSend(stack[2021], stack[2021]);
    }
    return rax;
}
再点进forwarding:
int ____forwarding___(int arg0, int arg1) {
    rsi = arg1;
    r13 = arg0;
    rcx = COND_BYTE_SET(NE);
    if (rsi != 0x0) {
            r12 = _objc_msgSend_stret;
    }
    else {
            r12 = _objc_msgSend;
    }
    rbx = *(r13 + rcx * 0x8);
    var_40 = *(r13 + rcx * 0x8 + 0x8);
    r15 = rcx * 0x8;
    if (rbx >= 0x0) goto loc_12c9ba;

loc_12c9ba:
    var_48 = r15;
    r15 = rsi;
    var_38 = r12;
    r12 = object_getClass(rbx);
    var_50 = class_getName(r12);
    if (class_respondsToSelector(r12, @selector(forwardingTargetForSelector:)) == 0x0) goto loc_12ca57;

会发现这么一段,if (class_respondsToSelector(r12,@selector(forwardingTargetForSelector:)) == 0x0) 如果forwardingTargetForSelector方法实现了的话,会继续进行转发,如果没有实现的话,会发现还有这么一段代码:

loc_12ca78:
    var_48 = r13;
    if (class_respondsToSelector(r12, @selector(methodSignatureForSelector:)) == 0x0) goto loc_12ce0d;

loc_12ce0d:
    rbx = class_getSuperclass(r12);
    r14 = object_getClassName(var_38);
    if (rbx == 0x0) {
            _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- did you forget to declare the superclass of '%s'?", var_38, r14, object_getClassName(var_38), r9, stack[2037]);
    }
    else {
            _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- trouble ahead", var_38, r14, r8, r9, stack[2037]);
    }
    goto loc_12ce6d;

loc_12ce6d:
    r15 = sel_getName(var_40);
    r8 = sel_getUid(r15);
    if (r8 != var_40) {
            _CFLog(0x4, @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort", var_40, r15, r8, r9, stack[2037]);
    }
    if (class_respondsToSelector(object_getClass(var_38), @selector(doesNotRecognizeSelector:)) != 0x0) {
            [var_38];
            asm { ud2 };
            rax = loc_12ced8(rdi, rsi);
    }
    else {
            _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort", var_38, object_getClassName(var_38), r8, r9, stack[2037]);
            asm { ud2 };
            rax = loc_12ceff();
    }
    return rax;

loc_12ca96:
    r12 = var_38;
    r14 = [r12 methodSignatureForSelector:var_40];
    if (r14 == 0x0) goto loc_12ce6d;

loc_12cab6:
    rbx = [r14 _frameDescriptor];
    if (((*(int16_t *)(*rbx + 0x22) & 0xffff) >> 0x6 & 0x1) != r15) {
            rax = sel_getName(var_40);
            r8 = "";
            rcx = r8;
            if ((*(int16_t *)(*rbx + 0x22) & 0xffff & 0x40) == 0x0) {
                    rcx = " not";
            }
            rdx = rax;
            if (r15 == 0x0) {
                    r8 = " not";
            }
            _CFLog(0x4, @"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'.  Signature thinks it does%s return a struct, and compiler thinks it does%s.", rdx, rcx, r8, r9, stack[2037]);
    }
    var_50 = rbx;
    if (class_respondsToSelector(object_getClass(r12), @selector(_forwardStackInvocation:)) != 0x0) {
            if (*____forwarding___.onceToken != 0xffffffffffffffff) {
                    dispatch_once(____forwarding___.onceToken, ^ {/* block implemented at ______forwarding____block_invoke */ } });
            }
            r13 = [NSInvocation requiredStackSizeForSignature:r14];
            rdx = *____forwarding___.invClassSize;
            r12 = rsp - (rdx + 0xf & 0xfffffffffffffff0);
            memset(r12, 0x0, rdx);
            objc_constructInstance(*____forwarding___.invClass, r12);
            var_40 = r13;
            [r12 _initWithMethodSignature:r14 frame:var_48 buffer:r12 - (r13 + 0xf & 0xfffffffffffffff0) size:r13];
            [var_38 _forwardStackInvocation:r12];
            r15 = 0x1;
    }
    else {
            if (class_respondsToSelector(object_getClass(r12), @selector(forwardInvocation:)) != 0x0) {
                    rax = [NSInvocation _invocationWithMethodSignature:r14 frame:var_48];
                    rdi = r12;
                    r12 = rax;
                    [rdi forwardInvocation:r12];
            }
            else {
                    rcx = object_getClassName(r12);
                    rdx = r12;
                    r12 = 0x0;
                    _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message", rdx, rcx, r8, r9, stack[2037]);
            }
            var_40 = 0x0;
            r15 = 0x0;
    }
    rax = var_50;

可以猜出来,r14即是methodSignatureForSelector:获取到的方法前面,然后接下来会判断并最后执行forwardInvocation进行消息重定向。

至此,iOS Runtime动态方法解析和消息转发流程已执行完成。最后一步消息转发是闭源的,过程比较模糊,只作为一个了解,我们也可以通过

        instrumentObjcMessageSends(YES);
        test code
        instrumentObjcMessageSends(NO);

去打印runtime消息执行并生成文件,文件目录在/private/tmp/ 文件夹,找到最新的 msgSends-xxxx文件同样可以看到消息转发的执行流程。