美文网首页
iOS底层原理:消息转发机制

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

作者: 打碟的DJ | 来源:发表于2020-10-01 14:52 被阅读0次

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

    分析下上面的代码:

    • 1、首先分析两个参数behaviorLOOKUP_RESOLVER
    // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
        // receiver and selector already in x0 and x1
        mov x2, x16
        mov x3, #3
        bl  _lookUpImpOrForward
    
    • behavior:在快速查找的流程中我们知道behavior其实就是LOOKUP_INITIALIZE | LOOKUP_RESOLVER,而LOOKUP_INITIALIZE = 1LOOKUP_RESOLVER = 2,那么|运算后的结果为0011,也就是3,即behavior = 3
    • LOOKUP_RESOLVER:搜索可得知LOOKUP_RESOLVER = 2
    • 2、那么第一次behavior & LOOKUP_RESOLVER的结果为1,会执行resolveMethod_locked,同时behavior ^= LOOKUP_RESOLVER后,behavior = 1
      • 2.1、那么下次会直接再次运行resolveMethod_locked中的resolveInstanceMethod方法,而在resolveInstanceMethod中会调用lookUpImpOrNil,也就是调用了lookUpImpOrForward(obj, sel, cls, behavior | LOOKUP_CACHE | LOOKUP_NIL),此时再次走入lookUpImpOrForward方法中,但是此时的behavior12
      • 2.2、那么再次执行到behavior & LOOKUP_RESOLVER ,也就是12 & 2的结果1100 & 0010,即0,所以之间进入了下一步调用log_and_fill_cache方法,这个方法就是将方法加入到缓存中。
    • 3、执行完resolveMethod_locked中的resolveInstanceMethod后,会再次执行一次lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE),此时的behavior1,那么behavior | LOOKUP_CACHE的结果为0001 | 0100,即0101,也就是5
    • 4、当再次进入lookUpImpOrForward方法时,此时会先去缓存中查找,因为在动态方法决议的过程中,已经将该方法加入了缓存中,并且5 & 4,结果为4,所以此次会走done_nolock,相关方法如下:
        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;
        }
    
    • 5、最后会执行判断(behavior & LOOKUP_NIL) && imp == forward_imp,即5 & 8 && 1,结果是0,直接返回了imp
    • 6、最后会调用到resolveInstanceMethod方法,当发现resolveInstanceMethod仍然为做处理时,会执行forwardingTargetForSelector,当forwardingTargetForSelector也未作处理时,会执行methodSignatureForSelector,然后当methodSignatureForSelector也未作处理时,会直接执行doesNotRecognizeSelector方法
    消息转发流程

    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

    相关文章

      网友评论

          本文标题:iOS底层原理:消息转发机制

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