美文网首页
八、消息流程之慢速查找

八、消息流程之慢速查找

作者: Mlqq | 来源:发表于2021-04-08 10:27 被阅读0次

    在Object-C中调方法在底层就是调objc_msgSend进行发送消息,消息发送时先在Class的cache中查找imp,这一步为快速查找,在cache中没有找到,就会在bits中查找,这一步为慢速查找。

    1、慢速查找方法定位

    结尾找到了MethodTableLookup,在搜索一下MethodTableLookup的定义:

    image.png

    这里只需要看bl _lookUpImpOrForward,这个跳转语句,这里是跳到_lookUpImpOrForward这个方法了,全局搜一下:

    image.png

    基本上都是跳转和调用,没看到调用。可以去掉下划线在搜索一下:

    image.png

    这就是由汇编调到C方法了,终于不用翻译汇编了。

    2、慢速查找流程

    先来浏览一下lookUpImpOrForward

    IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
    {
        const IMP forward_imp = (IMP)_objc_msgForward_impcache;
        IMP imp = nil;
        Class curClass;
    
        runtimeLock.assertUnlocked();
    
        if (slowpath(!cls->isInitialized())) {
            // The first message sent to a class is often +new or +alloc, or +self
            // which goes through objc_opt_* or various optimized entry points.
            //
            // However, the class isn't realized/initialized yet at this point,
            // and the optimized entry points fall down through objc_msgSend,
            // which ends up here.
            //
            // We really want to avoid caching these, as it can cause IMP caches
            // to be made with a single entry forever.
            //
            // Note that this check is racy as several threads might try to
            // message a given class for the first time at the same time,
            // in which case we might cache anyway.
            behavior |= LOOKUP_NOCACHE;
        }
    
        // 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();
    
        // We don't want people to be able to craft a binary blob that looks like
        // a class but really isn't one and do a CFI attack.
        //
        // To make these harder we want to make sure this is a class that was
        // either built into the binary or legitimately registered through
        // objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair.
        checkIsKnownClass(cls);
    
        cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
        // runtimeLock may have been dropped but is now locked again
        runtimeLock.assertLocked();
        curClass = cls;
    
        // The code used to lookup the class's cache again right after
        // we take the lock but for the vast majority of the cases
        // evidence shows this is a miss most of the time, hence a time loss.
        //
        // The only codepath calling into this without having performed some
        // kind of cache lookup is class_getInstanceMethod().
    
        for (unsigned attempts = unreasonableClassCount();;) {
            if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
    #if CONFIG_USE_PREOPT_CACHES
                imp = cache_getImp(curClass, sel);
                if (imp) goto done_unlock;
                curClass = curClass->cache.preoptFallbackClass();
    #endif
            } else {
                // curClass method list.
                Method meth = getMethodNoSuper_nolock(curClass, sel);
                if (meth) {
                    imp = meth->imp(false);
                    goto done;
                }
    
                if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
                    // No implementation found, and method resolver didn't help.
                    // Use forwarding.
                    imp = forward_imp;
                    break;
                }
            }
    
            // Halt if there is a cycle in the superclass chain.
            if (slowpath(--attempts == 0)) {
                _objc_fatal("Memory corruption in class list.");
            }
    
            // Superclass cache.
            imp = cache_getImp(curClass, sel);
            if (slowpath(imp == forward_imp)) {
                // Found a forward:: entry in a superclass.
                // Stop searching, but don't cache yet; call method
                // resolver for this class first.
                break;
            }
            if (fastpath(imp)) {
                // Found the method in a superclass. Cache it in this class.
                goto done;
            }
        }
    
        // No implementation found. Try method resolver once.
    
        if (slowpath(behavior & LOOKUP_RESOLVER)) {
            behavior ^= LOOKUP_RESOLVER;
            return resolveMethod_locked(inst, sel, cls, behavior);
        }
    
     done:
        if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
    #if CONFIG_USE_PREOPT_CACHES
            while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
                cls = cls->cache.preoptFallbackClass();
            }
    #endif
            log_and_fill_cache(cls, imp, sel, inst, curClass);
        }
     done_unlock:
        runtimeLock.unlock();
        if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
            return nil;
        }
        return imp;
    }
    

    代码的行数并不是很多,来逐行分析:
    1、const IMP forward_imp = (IMP)_objc_msgForward_impcache;,初始化一个impforward_imp变量,看看_objc_msgForward_impcache是怎么定义的:

    image.png

    又跳到汇编里面来了,__objc_msgForward_impcache跳转到 __objc_msgForward,在__objc_msgForward里面x17变量调用__objc_forward_handler方法操作,看看__objc_forward_handler是怎么操作的:

    image.png

    看到这里熟不熟悉,如果不熟悉看下面的:

    image.png

    这个变量就是在经历慢速查找、消息动态决议等之后条用的方法。这里我看还可以看到,对底层而言是不存在类方法还是实例方法的,+-的判断是根据是不是元类来认为判断的。

    2、cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);初始化当前的类,来看看怎么初始化的,一步步跟进来找到OC的初始化过程:

    static Class realizeClassWithoutSwift(Class cls, Class previously)
    {
        runtimeLock.assertLocked();
    
        class_rw_t *rw;
        Class supercls;
        Class metacls;
    
        if (!cls) return nil;
        if (cls->isRealized()) {
            validateAlreadyRealizedClass(cls);
            return cls;
        }
        ASSERT(cls == remapClass(cls));
    
        // fixme verify class is not in an un-dlopened part of the shared cache?
    
        auto ro = (const class_ro_t *)cls->data();
        auto isMeta = ro->flags & RO_META;
        if (ro->flags & RO_FUTURE) {
            // This was a future class. rw data is already allocated.
            rw = cls->data();
            ro = cls->data()->ro();
            ASSERT(!isMeta);
            cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
        } else {
            // Normal class. Allocate writeable class data.
            rw = objc::zalloc<class_rw_t>();
            rw->set_ro(ro);
            rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
            cls->setData(rw);
        }
    
        cls->cache.initializeToEmptyOrPreoptimizedInDisguise();
    
    #if FAST_CACHE_META
        if (isMeta) cls->cache.setBit(FAST_CACHE_META);
    #endif
    
        // Choose an index for this class.
        // Sets cls->instancesRequireRawIsa if indexes no more indexes are available
        cls->chooseClassArrayIndex();
    
        if (PrintConnecting) {
            _objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
                         cls->nameForLogging(), isMeta ? " (meta)" : "", 
                         (void*)cls, ro, cls->classArrayIndex(),
                         cls->isSwiftStable() ? "(swift)" : "",
                         cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
        }
    
        // Realize superclass and metaclass, if they aren't already.
        // This needs to be done after RW_REALIZED is set above, for root classes.
        // This needs to be done after class index is chosen, for root metaclasses.
        // This assumes that none of those classes have Swift contents,
        //   or that Swift's initializers have already been called.
        //   fixme that assumption will be wrong if we add support
        //   for ObjC subclasses of Swift classes.
        supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
        metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
    
    #if SUPPORT_NONPOINTER_ISA
        if (isMeta) {
            // Metaclasses do not need any features from non pointer ISA
            // This allows for a faspath for classes in objc_retain/objc_release.
            cls->setInstancesRequireRawIsa();
        } else {
            // Disable non-pointer isa for some classes and/or platforms.
            // Set instancesRequireRawIsa.
            bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
            bool rawIsaIsInherited = false;
            static bool hackedDispatch = false;
    
            if (DisableNonpointerIsa) {
                // Non-pointer isa disabled by environment or app SDK version
                instancesRequireRawIsa = true;
            }
            else if (!hackedDispatch  &&  0 == strcmp(ro->getName(), "OS_object"))
            {
                // hack for libdispatch et al - isa also acts as vtable pointer
                hackedDispatch = true;
                instancesRequireRawIsa = true;
            }
            else if (supercls  &&  supercls->getSuperclass()  &&
                     supercls->instancesRequireRawIsa())
            {
                // This is also propagated by addSubclass()
                // but nonpointer isa setup needs it earlier.
                // Special case: instancesRequireRawIsa does not propagate
                // from root class to root metaclass
                instancesRequireRawIsa = true;
                rawIsaIsInherited = true;
            }
    
            if (instancesRequireRawIsa) {
                cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
            }
        }
    // SUPPORT_NONPOINTER_ISA
    #endif
    
        // Update superclass and metaclass in case of remapping
        cls->setSuperclass(supercls);
        cls->initClassIsa(metacls);
    
        // Reconcile instance variable offsets / layout.
        // This may reallocate class_ro_t, updating our ro variable.
        if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);
    
        // Set fastInstanceSize if it wasn't set already.
        cls->setInstanceSize(ro->instanceSize);
    
        // Copy some flags from ro to rw
        if (ro->flags & RO_HAS_CXX_STRUCTORS) {
            cls->setHasCxxDtor();
            if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
                cls->setHasCxxCtor();
            }
        }
        
        // Propagate the associated objects forbidden flag from ro or from
        // the superclass.
        if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
            (supercls && supercls->forbidsAssociatedObjects()))
        {
            rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
        }
    
        // Connect this class to its superclass's subclass lists
        if (supercls) {
            addSubclass(supercls, cls);
        } else {
            addRootClass(cls);
        }
    
        // Attach categories
        methodizeClass(cls, previously);
    
        return cls;
    }
    

    这个看起来有点复杂,简化一下流程,具体看注释:

    static Class realizeClassWithoutSwift(Class cls, Class previously)
    {
    
        class_rw_t *rw;
        Class supercls;
        Class metacls;
    // 设置class的ro和rw
        auto ro = (const class_ro_t *)cls->data();
        auto isMeta = ro->flags & RO_META;
        if (ro->flags & RO_FUTURE) {
            // This was a future class. rw data is already allocated.
            rw = cls->data();
            ro = cls->data()->ro();
            cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
        } else {
            rw = objc::zalloc<class_rw_t>();
            rw->set_ro(ro);
            rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
            cls->setData(rw);
        }
    // 递归初始化父类和元类
    
        supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
        metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
    
    // 设置父类和元类
        cls->setSuperclass(supercls);
        cls->initClassIsa(metacls);
    
    //设置子类
        if (supercls) {
            addSubclass(supercls, cls);
        } else {
            addRootClass(cls);
        }
    
        // Attach categories
        methodizeClass(cls, previously);
    
        return cls;
    }
    

    可以看出初始化类的作用就是:
    (1)设置class的ro和rw
    (2)递归初始化父类和元类
    (3)设置父类和元类
    (4)设置子类
    经过以上4步,类的基本结构就已经完成了。另外,我们也可以看到一个类既有父类又有子类,可以说他就是双向链表结构。

    3、接下来就是一个for结构的死循环:
    3.1 第一次进来的时候现在又在自己的缓存中找一遍:

    imp = cache_getImp(curClass, sel);
    if (imp) goto done_unlock;
    

    看苹果的注释:

    image.png

    curClass = curClass->cache.preoptFallbackClass();执行这一行码之后curClass->cache.isConstantOptimizedCache(/* strict */true)这个判断条件就为false了,下一次进来就会走到else里面了。

    3.2 Method meth = getMethodNoSuper_nolock(curClass, sel);从当前类里面找,跟一下其实现方式:

    template<class getNameFunc>
    ALWAYS_INLINE static method_t *
    findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
    {
        ASSERT(list);
    
        auto first = list->begin();
        auto base = first;
        decltype(first) 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)getName(probe);
            
            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)getName((probe - 1))) {
                    probe--;
                }
                return &*probe;
            }
            
            if (keyValue > probeValue) {
                base = probe + 1;
                count--;
            }
        }
        
        return nil;
    }
    

    这个方法介绍两点:
    (1)这个查找方式是二分查找
    (2)会优先查找category的方法
    如果在当前类里面找到就会走到done

    log_and_fill_cache(cls, imp, sel, inst, curClass);
    
    static void
    log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
    {
    #if SUPPORT_MESSAGE_LOGGING
        if (slowpath(objcMsgLogEnabled && implementer)) {
            bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                          cls->nameForLogging(),
                                          implementer->nameForLogging(), 
                                          sel);
            if (!cacheIt) return;
        }
    #endif
        cls->cache.insert(sel, imp, receiver);
    }
    

    然后就走到cache.insert方法,这个方法在一篇中已经介绍过,就形成了一个完整的闭环。

    3.3 如果在当前类没有找到,就会走到:

    if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
         // No implementation found, and method resolver didn't help.
         // Use forwarding.
         imp = forward_imp;
         break;
    }
    

    条件slowpath((curClass = curClass->getSuperclass()) == nil),有两个作用将curClass指向父类,判断父类是否为nil,如果父类为nil,将imp赋值为forward_imp,然后出循环,注意这里用的是break,如果父类不为nil,就继续查找父类的cache。

    3.4

    // Superclass cache.
    imp = cache_getImp(curClass, sel);
    if (slowpath(imp == forward_imp)) {
        // Found a forward:: entry in a superclass.
        // Stop searching, but don't cache yet; call method
        // resolver for this class first.
        break;
    }
    if (fastpath(imp)) {
     // Found the method in a superclass. Cache it in this class.
      goto done;
    }
    

    查找父类的cache,如果找到imp == forward_imp也是break,如果不是goto done,都不满足接着下一个循环,一直到slowpath((curClass = curClass->getSuperclass()) == nil)这个条件满足跳出所有循环。

    4

     if (slowpath(behavior & LOOKUP_RESOLVER)) {
            behavior ^= LOOKUP_RESOLVER;
            return resolveMethod_locked(inst, sel, cls, behavior);
        }
    

    在for的死循环中凡是break的都会走到这里来,说明类自己以及父类都没有找到,最后会调resolveMethod_locked动态方法决议来处理,这个过程下一篇来介绍。

    • objc_msgSend流程总结:
    image.png

    相关文章

      网友评论

          本文标题:八、消息流程之慢速查找

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