iOS底层原理:消息转发机制

2020-10-01  本文已影响0人  打碟的DJ

iOS底层原理:objc_msgSend之缓存查找iOS底层原理:objc_msgSend之慢速查找 中我们已经分析了,当方法在快速查找和慢速查找流程都未找到时,会走动态方法决议消息转发流程。

动态方法决议消息转发流程是苹果提供给我们解决方法未实现的兜底方法。当方法未在快速查找慢速查找中找到实现时,会先走入动态方法决议,如果动态方法决议仍未对方法(即imp)进行处理时,会走入消息转发流程。

消息转发流程其实又分为了快速消息转发慢速消息转发

消息转发机制

动态方法决议

lookUpImpOrForward方法中其实有个判断条件用于判断是否进行动态方法决议的条件判断:

    // No implementation found. Try method resolver once.
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

分析下上面的代码:

// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
    // receiver and selector already in x0 and x1
    mov x2, x16
    mov x3, #3
    bl  _lookUpImpOrForward
    if (fastpath(behavior & LOOKUP_CACHE)) {
        imp = cache_getImp(cls, sel);
        if (imp) goto done_nolock;
    }
 done_nolock:
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
消息转发流程

resolveMethod_lockedf分析

iOS底层原理:objc_msgSend之慢速查找流程中,我们知道了最后在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);
}

在上面的源码中我们可以发现,动态方法决议其实又细分了类方法的动态方法决议实例方法的动态方法决议

实例方法的动态方法决议

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

1、在resolveInstanceMethod中,首先调用lookUpImpOrNil判断是否实现了resolveInstanceMethod,也就是我们说的动态方法决议
2、然后会调用objc_msgSend进行resolveInstanceMethod的消息转发,并将未找到的sel作为参数传出去
3、最后再次调用lookUpImpOrNil进行查询原来的方法是否有实现,也就是第二步是否有将imp进行处理,重新走lookUpImpOrForward流程。

类方法的动态方法决议

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

类方法的动态方法决议方法实现,其实和实例方法的动态方法决议实现基本流程是一样的,唯一的区别是类方法的动态决议在根元类还未找到之后,会调用一次根元类的指向的实例方法的实例方法的动态方法决议!lookUpImpOrNil(inst, sel, cls)判断是否指向根元类),也就是如果类方法没有实现,则会去找同名的实例方法。这点在isa的走位图中可以看出来。

消息转发

如果通过源码分析,其实到了动态方法决议resolveInstanceMethod方法之后,我们就很难分析到接下来需要走的流程了。那么我们可以通过一个系统提供的方法instrumentObjcMessageSends来查看下。可以发现我们可以再/tmp/目录下找到对应的文件msgSends-%d"

调用instrumentObjcMessageSends实现:

extern void instrumentObjcMessageSends(BOOL flag);

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        Person *person = [Person alloc];
        
        instrumentObjcMessageSends(YES); // 开启
        [person sayHello];
        instrumentObjcMessageSends(NO); // 关闭
        
        NSLog(@"Hello, World!  %@",person);
    }
    return 0;
}
消息准发流程日志

通过日志文件也可以分析出,当执行完动态方法决议时,会进入forwardingTargetForSelector方法,也就是我们常说的快速消息转发

还可以通过hopper disassembler来进行二进制分析,也可以分析出流程。

快速消息转发

快速消息转发

通过实现forwardingTargetForSelector,我们可以看到确实走到了该方法内,那么我们要如何在这里面进行处理呢?

替代者

可以看到,当我们将当前的接受者指定为Student时,会执行Student中实现的方法。

那么我们在此时通过runtime将改方法中获取到的aSelector使用动态添加的方式(class_addMethod),来实现,也可以进行快速消息转发

但是当我们在forwardingTargetForSelector中也未进行处理时,会走入慢速消息转发

慢速消息转发

forwardingTargetForSelector描述 methodSignatureForSelector描述

通过官方文档中的描述,我们可以知道需要在methodSignatureForSelector中返回一个NSInvocation对象后,才能进入forwardInvocation方法。当返回nil或者self的时候,就会进入doesNotRecognizeSelector方法。

anInvocation

从上图中我们可以看到,在anInvocation中,有targetselector属性,那么我们可以动态的修改这几个属性,来实现定制化。

动态修改target和selector

我们将原来Person中的sayHello方法,通过指定Student中的sayHey方法,来进行了消息转发。

如果在上诉几个流程中都未进行处理,则会进入我们常见的报错方法中了。即doesNotRecognizeSelector

上一篇下一篇

猜你喜欢

热点阅读