美文网首页
系统底层源码分析(19)——动态方法决议&消息转发

系统底层源码分析(19)——动态方法决议&消息转发

作者: 无悔zero | 来源:发表于2021-06-25 15:54 被阅读0次

    接着上篇文章(系统底层源码分析(18)——objc_msgSend)继续说:

    IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                           bool initialize, bool cache, bool resolver)
    {
        ...
        if (!cls->isRealized()) {
            realizeClass(cls);//准备-父类
        }
        ...
     retry:    
        runtimeLock.assertLocked();
    
        // Try this class's cache.
    
        imp = cache_getImp(cls, sel);//从缓存获取imp
        if (imp) goto done;//找到返回
        //缓存中没有找到就去查找方法列表
        // Try this class's method lists.
        {//局部作用域,可避免名字冲突
            Method meth = getMethodNoSuper_nolock(cls, sel);//查找
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, cls);//缓存
                imp = meth->imp;
                goto done;
            }
        }
        //子类找不到就递归找父类
        // Try superclass caches and method lists.
        {
            unsigned attempts = unreasonableClassCount();
            for (Class curClass = cls->superclass;
                 curClass != nil;
                 curClass = curClass->superclass)
            {
                // Halt if there is a cycle in the superclass chain.
                if (--attempts == 0) { ... }
                
                // Superclass cache.
                imp = cache_getImp(curClass, sel);//从父类缓存获取imp
                if (imp) {
                    if (imp != (IMP)_objc_msgForward_impcache) {
                        log_and_fill_cache(cls, imp, sel, inst, curClass);
                        goto done;
                    }
                    else {
                        break;
                    }
                }
                //缓存中没有找到就去查找父类方法列表
                // Superclass method list.
                Method meth = getMethodNoSuper_nolock(curClass, sel);//查找
                if (meth) {
                    log_and_fill_cache(cls, meth->imp, sel, inst, curClass);//缓存
                    imp = meth->imp;
                    goto done;
                }
            }
        }
        
        // 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;
    }
    
    • 报错
    1. 之前说到调用方法,底层会调用objc_msgSend,进入快速查找流程,找不到就进入慢速查找流程,然后来到lookUpImpOrForward,如果最后还找不到,就会调用_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
    
    
    ENTRY __objc_msgForward
    // Non-stret version
    
    MI_GET_EXTERN(r12, __objc_forward_handler)
    ldr r12, [r12]
    bx  r12
    
    END_ENTRY __objc_msgForward
    

    上述是汇编代码,调用流程是:

    __objc_msgForward_impcache -> __objc_msgForward -> __objc_forward_handler

    1. __objc_forward_handler会回到源码中,查找时需要去掉一个下划线:
    __attribute__((noreturn)) void 
    objc_defaultForwardHandler(id self, SEL sel)
    {
        _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
                    "(no message forward handler is installed)", 
                    class_isMetaClass(object_getClass(self)) ? '+' : '-', 
                    object_getClassName(self), sel_getName(sel), self);//打印的错误信息
    }
    void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
    

    打印信息是不是很熟悉,就是我们调用没有实现的方法时报的错:

    报错信息
    • 动态方法决议
    1. 如果调用没有实现的方法就会报错,但是系统给了挽救的机会,就在报错前进行处理的_class_resolveMethod函数:
    void _class_resolveMethod(Class cls, SEL sel, id inst)
    {
        if (! cls->isMetaClass()) {
            // try [cls resolveInstanceMethod:sel]
            _class_resolveInstanceMethod(cls, sel, inst);//实例方法决议
        } 
        else {
            // try [nonMetaClass resolveClassMethod:sel]
            // and [cls resolveInstanceMethod:sel]
            _class_resolveClassMethod(cls, sel, inst);//类方法决议
            if (!lookUpImpOrNil(cls, sel, inst, 
                                NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
            {//根据isa走位图,查找实例方法和类方法最终都走向NSObject类,所以再进行一次实例方法决议
                _class_resolveInstanceMethod(cls, sel, inst);//实例方法决议
            }
        }
    }
    
    1. 我们先来看对实例方法的处理,首先会查找resolveInstanceMethod
    static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
    {
        if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) //查找resolveInstanceMethod并缓存
        {
            // 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*/);//再找一次
        ...
    }
    
    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;
    }
    
    1. 找到之后因为会缓存,所以下次查找时,可以通过objc_msgSend更快查找并调用,这里要注意找的是系统方法resolveInstanceMethod
    + (BOOL)resolveInstanceMethod:(SEL)sel {
        return NO;
    }
    
    1. 这时可以通过在Person类中重写resolveInstanceMethod来进行处理(动态添加方法):
    //Person类中
    + (BOOL)resolveInstanceMethod:(SEL)sel{
        if (sel == @selector(say)) {
            IMP sayHelloIMP = class_getMethodImplementation(self, @selector(sayHello));
            Method sayHelloMethod = class_getInstanceMethod(self, @selector(sayHello));
            const char *sayHelloType = method_getTypeEncoding(sayHelloMethod);
            return class_addMethod(self, sel, sayHelloIMP, sayHelloType);
        }
        return [super resolveInstanceMethod:sel];
    }
    
    1. 返回后,回到第2步,这时再次lookUpImpOrNil,由于动态添加了方法,所以这次可以找到方法,然后返回后就回到第1步进行retry,重新查找并找到,所以就不会报错。

    2. 另外类方法处理也是差不多:

    /***********************************************************************
    * _class_resolveClassMethod
    * Call +resolveClassMethod, looking for a method to be added to class cls.
    * cls should be a metaclass.
    * Does not check if the method already exists.
    **********************************************************************/
    static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
    {
        assert(cls->isMetaClass());
    
        if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) //查找resolveClassMethod并缓存
        {
            // Resolver not implemented.
            return;
        }
    
        BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;//消息发送,快速查找
        bool resolved = msg(_class_getNonMetaClass(cls, inst), 
                            SEL_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(cls, sel, inst, 
                                 NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
        ...
    }
    
    //Person类中
    + (BOOL)resolveClassMethod:(SEL)sel{
        if (sel == @selector(cls_say)) {
            IMP sayHelloIMP = class_getMethodImplementation(objc_getMetaClass("Person"), @selector(cls_sayHello));
            Method sayHelloMethod = class_getInstanceMethod(objc_getMetaClass("Person"), @selector(cls_sayHello));
            const char *sayHelloType = method_getTypeEncoding(sayHelloMethod);
            return class_addMethod(objc_getMetaClass("Person"), sel, sayHelloIMP, sayHelloType);
        }
        return [super resolveClassMethod:sel];
    }
    
    • 消息转发

    如果动态方法决议也没有处理,就会进入消息转发流程(简单来说就是交给别人处理):

    1. 首先会进入快速转发:
    //Person类中
    - (id)forwardingTargetForSelector:(SEL)aSelector{
        if (aSelector == @selector(say)) {
            return [Teacher alloc];//返回对象
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    
    1. 如果快速转发也没有处理,就进入慢速转发流程:

    2-1. methodSignatureForSelector返回SEL方法的签名,返回的签名是根据方法的参数来封装的。这个函数让重载方有机会抛出一个函数的签名,用于生成NSInvocation,再由后面的 forwardInvocation去执行:

    //Person类中
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
        if (aSelector == @selector(say)) { 
            return [NSMethodSignature signatureWithObjCTypes:"v@:"];//方法签名
        }
        return [super methodSignatureForSelector:aSelector];
    }
    

    2-2. 在forwardInvocation里可以将NSInvocation多次转发到多个对象中,这也是这种方式灵活的地方。(而forwarding TargetForSelectorSelec只能以Selector的形式转向一个对象):

    //Person类中
    - (void)forwardInvocation:(NSInvocation *)anInvocation{
       SEL aSelector = [anInvocation selector];
       Teacher *teacher = [Teacher alloc];
       if ([teacher respondsToSelector:aSelector])
           [anInvocation invokeWithTarget:teacher];
       else
           [super forwardInvocation:anInvocation];
    }
    
    • 实现forwardInvocation后,就算不处理应用也不会再崩溃。
    没有实现的方法处理流程图
    • 补充
    1. 为了更充分说明这些流程的有效性,我们可以通过instrumentObjcMessageSends获取系统日志:
    extern void instrumentObjcMessageSends(BOOL flag);//系统方法
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Student *student = [Student alloc] ;
            instrumentObjcMessageSends(true);//路径在/tmp/
            [student say];
            instrumentObjcMessageSends(false);
        }
        return 0;
    }
    
    1. 然后直接前往/tmp(文件夹路径),然后打开msgSends-前缀的文件:
    //动态方法决议
    + Student NSObject resolveInstanceMethod:
    + Student NSObject resolveInstanceMethod:
    //消息转发-快速
    - Student NSObject forwardingTargetForSelector:
    - Student NSObject forwardingTargetForSelector:
    //消息转发-慢速
    - Student Student methodSignatureForSelector:
    - Student Student methodSignatureForSelector:
    ...
    //还会调用一次resolveInstanceMethod,因为methodSignatureForSelector返回的签名需要查找方法来匹配,所以进行lookUpImpOrForward查询导致又调用了resolveInstanceMethod
    + Student NSObject resolveInstanceMethod:
    + Student NSObject resolveInstanceMethod:
    //
    - Student Student forwardInvocation:
    
    + NSInvocation NSObject initialize
    + NSInvocation NSInvocation _invocationWithMethodSignature:frame:
    - NSMethodSignature NSMethodSignature frameLength
    ...
    

    相关文章

      网友评论

          本文标题:系统底层源码分析(19)——动态方法决议&消息转发

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