美文网首页
iOS 底层原理 - 消息查找流程

iOS 底层原理 - 消息查找流程

作者: yan0_0 | 来源:发表于2020-03-08 14:33 被阅读0次

    我们从之前的方法的本质objc_msgSend分析一篇中得知了解到objc_msgSend发送消息进入到了汇编,找不到缓存的时候就会来到__objc_msgSend_uncached -- > MethodTableLookup --> __class_lookupMethodAndLoadCache3,现在我们通过断点调试来分析一下。

    断点调试分析

    在调用方法处打上断点,开启Always Show Disassembly


    屏幕快照 2020-03-07 下午10.51.47.png 屏幕快照 2020-03-07 下午10.55.50.png

    然后进入objc_msgSend,往下翻,看到下面有个_objc_msgSend_uncached方法


    屏幕快照 2020-03-07 下午10.57.15.png

    在这里加断点,继续往下走,找到方法_class_lookupMethodAndLoadCache3


    屏幕快照 2020-03-07 下午10.59.37.png

    在源码中全局搜索_class_lookupMethodAndLoadCache3方法,找到方法实现

    extern IMP _class_lookupMethodAndLoadCache3(id, SEL, Class);
    

    点进去

    IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
    {
        return lookUpImpOrForward(cls, sel, obj, 
                                  YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
    }
    
    

    到此,方法查找成功从汇编过渡到C++,这就是真正的方法查找实现。

    代码分析方法查找流程

    对象方法测试

    对象的实例方法 - 自己有 - 返回自己
    对象的实例方法 - 自己没有 - 父类有 - 返回父类的
    对象的实例方法 - 自己没有 - 父类也没有 - 找父类的父类 - NSObject有 - 返回NSObject的
    对象的实例方法 - 自己没有 - 父类也没有 - 找父类的父类 - NSObject也没有 - 崩溃

    类方法测试

    类方法 - 自己有 - 返回自己
    类方法 - 自己没有 - 父类有 - 返回父类的
    类方法 - 自己没有 - 父类也没有 - 找父类的父类 - NSObject有 - 返回NSObject的
    类方法 - 自己没有 - 父类也没有 - 找父类的父类 - NSObject也没有 - 崩溃
    类方法 - 自己没有 - 父类也没有 - 找父类的父类 - NSObject也没有 - 但是有对象方法 - 返回NSObject的实例方法

    这里通过一个经典的isa走位图来解释说明


    isa流程图.png

    源码分析方法查找流程

    这里我们直接定位到_class_lookupMethodAndLoadCache3这个方法

    IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
    {
        return lookUpImpOrForward(cls, sel, obj, 
                                  YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
    }
    

    然后进入lookUpImpOrForward这个方法,这里cache传的值为NO,证明缓存中没有找到,需要进行方法查找,也就是慢速方法查找流程
    然后开始分析lookUpImpOrForward这个方法

    IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                           bool initialize, bool cache, bool resolver)
    {
        IMP imp = nil;
        bool triedResolver = NO;
    
        runtimeLock.assertUnlocked();
    
        // Optimistic cache lookup   这里cache传的NO,所以不走这里的逻辑
        if (cache) {
            imp = cache_getImp(cls, sel);
            if (imp) return imp;
        }
    
        // runtimeLock is held during isRealized and isInitialized checking
        // to prevent races against concurrent realization.
    
        // runtimeLock is held during method search to make
        // method-lookup + cache-fill atomic with respect to method addition.
        // Otherwise, a category could be added but ignored indefinitely because
        // the cache was re-filled with the old value after the cache flush on
        // behalf of the category.
    
        runtimeLock.lock();   //这里加了锁,是为了防止同时查找两个方法时返回imp错误
        checkIsKnownClass(cls);
    
    //从类里查找方法,首先要处理好类,做准备条件,判断类有没有加载好,如果没有加载好,那就先加载一下类信息,准备好父类、元类
        if (!cls->isRealized()) {
            realizeClass(cls);
        }
        if (initialize  &&  !cls->isInitialized()) {
            runtimeLock.unlock();
            _class_initialize (_class_getNonMetaClass(cls, inst));
            runtimeLock.lock();
            // If sel == initialize, _class_initialize will send +initialize and 
            // then the messenger will send +initialize again after this 
            // procedure finishes. Of course, if this is not being called 
            // from the messenger then it won't happen. 2778172
        }
    
        
     retry:    
        runtimeLock.assertLocked();
    
        // Try this class's cache.
    
        imp = cache_getImp(cls, sel);
        if (imp) goto done;
    
      //这里大括号意思是为了形成局部域
      // 从当前类上查找,这里是从类的方法列表中查找 IMP。通过 getMethodNoSuper_nolock 查找 Method,找到了之后就调用 log_and_fill_cache 进行缓存的填充,然后返回 imp。
        // 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) {
                    _objc_fatal("Memory corruption in class list.");
                }
                
                
                // Superclass cache.从缓存中查找,找到就返回img找不到就break
                imp = cache_getImp(curClass, sel);
                if (imp) {
                    if (imp != (IMP)_objc_msgForward_impcache) {
                        // Found the method in a superclass. Cache it in this class.
                        log_and_fill_cache(cls, imp, sel, inst, curClass);
                        goto done;
                    }
                    else {
                        // Found a forward:: entry in a superclass.
                        // Stop searching, but don't cache yet; call method 
                        // resolver for this class first.
                        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.
     //如果方法仍然没找到,首先会进行动态方法解析_class_resolveMethod,在这个过程中,系统会调用一次已经存在的事先定义好的两个类方法,在这里给我们提供了一次容错的机会,
        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;
    }
    
    

    这里是通过getMethodNoSuper_nolock进行方法列表查找

    getMethodNoSuper_nolock(Class cls, SEL sel)
    {
        runtimeLock.assertLocked();
    
        assert(cls->isRealized());
        // fixme nil cls? 
        // fixme nil sel?
    
        for (auto mlists = cls->data()->methods.beginLists(), 
                  end = cls->data()->methods.endLists(); 
             mlists != end;
             ++mlists)
        {
            method_t *m = search_method_list(*mlists, sel);
            if (m) return m;
        }
    
        return nil;
    }
    
    

    用的二分法快速的查找方法

    static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list)
    {
        assert(list);
    
        const method_t * const first = &list->first;
        const method_t *base = first;
        const method_t *probe;
        uintptr_t keyValue = (uintptr_t)key;
        uint32_t count;
        
        for (count = list->count; count != 0; count >>= 1) {
            probe = base + (count >> 1);
            
            uintptr_t probeValue = (uintptr_t)probe->name;
            
            if (keyValue == probeValue) {
                // `probe` is a match.
                // Rewind looking for the *first* occurrence of this value.
                // This is required for correct category overrides.
                while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
                    probe--;
                }
                return (method_t *)probe;
            }
            
            if (keyValue > probeValue) {
                base = probe + 1;
                count--;
            }
        }
        
        return nil;
    }
    

    如果所有的方法都找不到,就会来到这里

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

    首先会进行动态方法解析_class_resolveMethod,在这个过程中,系统会调用一次已经存在的事先定义好的两个类方法,在这里给我们提供了一次容错的机会。首先会判断cls是不是元类,如果cls不是元类的话,说明调用的是实例方法,那就就会调用_class_resolveInstanceMethod函数,如果是元类的话,说明调用的是类方法,那么就会调用_class_resolveClassMethod函数,并且调用完后会再次查找一下sel的指针,找到了就会返回,如果还是找不到的话会调用_class_resolveInstanceMethod函数,这里可以用上面的isa走位图解释:
    类方法 - 自己没有 - 父类也没有 - 找父类的父类 - NSObject也没有 - 但是有对象方法 - 返回NSObject的实例方法

    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*/)) 
            {
                _class_resolveInstanceMethod(cls, sel, inst);
            }
        }
    }
    

    如果我们没有做任何处理,那么则进入消息转发阶段,消息转发阶段全局搜索一下该方法。在其汇编方法内部,调用了__objc_msgForward

    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);
    

    全局搜索_objc_msgForward_impcache,进入到汇编


    屏幕快照 2020-03-08 上午11.05.17.png

    然后看到下面执行方法_objc_forward_handler,继续全局搜索,看到的熟悉的找不到方法的报错信息

    void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
    
    __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);
    }
    
    

    总结

    OC的消息机制可以分为一下三个阶段:
    消息发送阶段:先是通过objc_msgSend的快速缓存查找,之后再通过对类以及父类的方法列表进行二分法常规查找。
    动态解析阶段:如果消息发送阶段没有找到方法,则会进入动态解析阶段,负责动态的添加方法实现;
    消息转发阶段:如果也没有实现动态解析方法,则会进行消息转发阶段,将消息转发给可以处理消息的接受者来处理,消息转发处理失败之后则报错crash。
    之后便是消息转发的功能了,这里先附上流程图,下篇我们再继续分析转发的流程。


    消息转发流程.png

    相关文章

      网友评论

          本文标题:iOS 底层原理 - 消息查找流程

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