美文网首页
IOS底层(十四): 消息流程(二)慢速查找

IOS底层(十四): 消息流程(二)慢速查找

作者: ShawnAlex | 来源:发表于2021-05-24 23:12 被阅读0次

    OC底层源码/原理合集

    建议先看下
    IOS底层(十三): 消息流程之快速查找

    • sel : 方法编号, 可以理解成一本书的目录, 可通过对应名称找到页码

    • imp : 函数指针地址, 可以理解成书的页码, 方便找到具体实现的函数

    objc_msgSend 慢速查找流程分析

    之前我们接触过, 如果方法在cache中找到对应的sel, imp则直接CacheHit缓存命中, 如果没有找到则会走一个__objc_msgSend_uncached方法

    objc_msgSend_uncached

    看下__objc_msgSend_uncached源码, 其中MethodTableLookup查询方法列表`为核心方法

        STATIC_ENTRY __objc_msgSend_uncached
        UNWIND __objc_msgSend_uncached, FrameWithNoSaves
    
        // THIS IS NOT A CALLABLE C FUNCTION
        // Out-of-band p15 is the class to search
        
        MethodTableLookup //  核心方法 查询方法列表
        TailCallFunctionPointer x17
    
        END_ENTRY __objc_msgSend_uncached
    
    
        STATIC_ENTRY __objc_msgLookup_uncached
        UNWIND __objc_msgLookup_uncached, FrameWithNoSaves
    
        // THIS IS NOT A CALLABLE C FUNCTION
        // Out-of-band p15 is the class to search
        
        MethodTableLookup
        ret
    
        END_ENTRY __objc_msgLookup_uncached
    

    接下来看下MethodTableLookup这个源码, 其中lookUpImpOrForward为核心代码

    .macro MethodTableLookup
        
        SAVE_REGS MSGSEND
    
        // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
        // receiver and selector already in x0 and x1
        mov x2, x16
        mov x3, #3
        bl  _lookUpImpOrForward // 核心代码
    
        // IMP in x0
        mov x17, x0
    
        RESTORE_REGS MSGSEND
    
    .endmacro
    

    接下来我们验证下慢速查找, 是否会按照我们想的走lookUpImpOrForward这个方法

    定义一个sayHello实例方法, 并加个断点

    main

    断到后, 开启汇编模式(Debug → Debug worlflow → Always show Disassembly), 可看到走了objc_msgsend方法

    objc_msgSend

    objc_msgsend 处加断点, control + stepinto, 进入objc_msgsend

    objc_msgSend

    0x7FFFFFFFFFF8其中这里是个掩码, 这块就是操作缓存, 发现没有找到, 排除一些objc_debug_taggedpointer_classes影响, 往下走可看到走了objc_msgsend_uncached方法

    _objc_msgSend_uncached

    同理断点objc_msgsend_uncached , control + stepinto进入, 可看到走了lookUpImpOrForward方法

    lookUpImpOrForward

    lookUpImpOrForward

    看下消息转发的核心lookUpImpOrForward源码, (lookUpImpOrForward 涉及内容比较多, 这里我分2篇文章进行描述)

    // 消息转发核心方法
    IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
    {
        // 源码分析见:  详细分析一:  _objc_msgForward_impcach
        // 这里首先定义一个forward_imp, 
        // 后面当前类链, 父类链都找不到我们要找的imp时候, 会令 imp = forward_imp
        const IMP forward_imp = (IMP)_objc_msgForward_impcache;
       
        // 初始定义个imp设置为 null
        IMP imp = nil;
    
         // 定义个变量类 curClass, 后面会给赋值
        Class curClass;
        
        runtimeLock.assertUnlocked();
    
        // 快速查找, 判断是否有 +new or +alloc, or +self 初始化或者实现 
        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.
    
            // C语言中的 |= 意思为:按位或后赋值
           // 例子:x = 0x02;  x  |= 0x01;
           // 按位或的结果为:0x03 等同于0011
           // LOOKUP_NOCACHE = 8,
            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
        checkIsKnownClass(cls);
        
    
    
        // 判断当前类是否实现/初始化, 如果没有, 做一个初始化方法
        // 同时底层还做了递归实现, 把整个 继承链 都确定下来(父类, 元类的都有)
        // supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
        // metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
    
        // 源码分析见:  详细分析三:  realizeAndInitializeIfNeeded_locked
        cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
        // runtimeLock may have been dropped but is now locked again
        runtimeLock.assertLocked();
    
        // 初始化/实现 之后令 curClass 等于 cls
        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();;) {
    
    // inline bool isConstantOptimizedCache(bool strict = false, uintptr_t empty_addr = 0) const { return false; }
    
    // 快速查找,如果找到则直接返回imp
    // 目的:防止多线程操作时,刚好调用函数,此时缓存进来了
    // 判断当前类是否有缓存优化, 其实可以理解成是否以及配置了缓存
            if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
    #if CONFIG_USE_PREOPT_CACHES
    // 如果当前类配置了缓存, 直接走cache_getImp方法根据sel获取imp
                imp = cache_getImp(curClass, sel);
                if (imp) goto done_unlock;
                curClass = curClass->cache.preoptFallbackClass();
    #endif
            } else {
                // 缓存没有方法 
                // curClass method list.
                // 做个比喻: 如果我自己可以就不麻烦爸爸了, 去自己类的方法列表里面去找(查找class → data)
                // 根据sel查找自己的 method list(采用二分查找算法),  如果有, 则返回imp
                // 源码分析见:  详细分析四:  getMethodNoSuper_nolock
                Method meth = getMethodNoSuper_nolock(curClass, sel);
                if (meth) {
                // 如果找到了就走后面的done 方法
                // done:
                //    if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
                //#if CONFIG_USE_PREOPT_CACHES
                //        while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
                //            cls = cls->cache.preoptFallbackClass();
                //      }
                     
                    imp = meth->imp(false);
    
                    goto done;
                }
    
                // 这里面令curClass 为其父类, 如果父类也没有令 imp = forward_imp
                if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
                    // No implementation found, and method resolver didn't help.
                    // Use forwarding.
                    imp = forward_imp; // forward就是那个报错方法
                    break;
                }
            }
    
            // Halt if there is a cycle in the superclass chain.
            if (slowpath(--attempts == 0)) {
                _objc_fatal("Memory corruption in class list.");
            }
             
            // Superclass cache.
            // 自己方法没有找到去父类方法查找, 所以之前要确定继承链, 方便父类递归查找继续调用lookup 方法
            // returns:
            // - the cached IMP when one is found
            // - nil if there's no cached value and the cache is dynamic
            // - `value_on_constant_cache_miss` if there's no cached value and the cache is     preoptimized
            // extern "C" IMP cache_getImp(Class cls, SEL sel, IMP value_on_constant_cache_miss = nil);
    
            // 父类查找缓存
            // 源码分析见:  详细分析五:  cache_getImp
            // 依次查找父类的缓存是否有指定方法
            imp = cache_getImp(curClass, sel);
    
            // 如果还是没有找到,  走下面2个判断
            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.
        // 当imp = forward_imp时(即父类也没查找到), 会走这里的return方法
        // RESOLVER就是动态方法决议
        if (slowpath(behavior & LOOKUP_RESOLVER)) {
            behavior ^= LOOKUP_RESOLVER;
            return resolveMethod_locked(inst, sel, cls, behavior);
        }
    
     done:
    // 如果找到了imp 直接存入缓存, 方便下次快速查找
    // 底层关键代码 cls->cache.insert(sel, imp, receiver);
    // objc_msgsend → 先查找缓存 → 缓存中没有再慢速查找 → 二分查找自己 → 找到存入缓存 → 下次objc_msgsend → 查找缓存
    
    
        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;
    }
    

    详细分析一: _objc_msgForward_impcache

     _objc_msgForward_impcache 源码实现
    
    /********************************************************************
    *
    * id _objc_msgForward(id self, SEL _cmd,...);
    *
    * _objc_msgForward is the externally-callable
    *   function returned by things like method_getImplementation().
    * _objc_msgForward_impcache is the function pointer actually stored in
    *   method caches.
    *
    ********************************************************************/
    这里需要在汇编查找
    // 1. 进入__objc_msgForward_impcache 之后发现走 __objc_msgForward
    // 2. 进入__objc_msgForward 之后走 __objc_forward_handler
    
        STATIC_ENTRY __objc_msgForward_impcache
    
        // No stret specialization.
        b   __objc_msgForward
    
        END_ENTRY __objc_msgForward_impcache
    
        ENTRY __objc_msgForward
    
           // 关键代码__objc_forward_handler
        adrp    x17, __objc_forward_handler@PAGE
        ldr p17, [x17, __objc_forward_handler@PAGEOFF]
        TailCallFunctionPointer x17
        
        END_ENTRY __objc_msgForward
    
    
    
    /***********************************************************************
    * objc_setForwardHandler
    **********************************************************************/
    
    // _objc_forward_handler 方法
    
    #if !__OBJC2__
    
    // Default forward handler (nil) goes to forward:: dispatch.
    void *_objc_forward_handler = nil;
    void *_objc_forward_stret_handler = nil;
    
    #else
    
    // Default forward handler halts the process.
    __attribute__((noreturn, cold)) void
    objc_defaultForwardHandler(id self, SEL sel)
    {
    // 核心源码部分
    // 可看到每次imp找不到时候必然会走这里, 报这个错误(就是我们常见的方法找不到错误)
    // 同时看到这里面"+", "-"是苹果人为给写的, 方法底层根本不存在什么类方法, 实例方法
    // 类方法对于元类来说就是实例方法, 实例方法对于类来说也是实例方法 
    
        _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;
    
    

    这里普及个查询源码小知识点

    • C/C++中调用汇编, 去查找汇编时, C/C++调用的方法需要多加一个下划线
    • 汇编中调用C/C++方法时, 去查找C/C++方法, 需要将汇编调用的方法去掉一个下划线

    详细分析二: checkIsKnownClass

    // 判断当前的类是否是被认可的类
    // 判断方法列表, 属性列表等是否已缓存形式写入内存或者加载到已知类中
    // 查到才能有相应缓存结构
    static void
    checkIsKnownClass(Class cls)
    {
        if (slowpath(!isKnownClass(cls))) {
            _objc_fatal("Attempt to use unknown class %p.", cls);
        }
    }
    
    ALWAYS_INLINE
    static bool
    isKnownClass(Class cls)
    {
        if (fastpath(objc::dataSegmentsRanges.contains(cls->data()->witness, (uintptr_t)cls))) {
            return true;
        }
        auto &set = objc::allocatedClasses.get();
        return set.find(cls) != set.end() || dataSegmentsContain(cls);
    }
    

    详细分析三: realizeAndInitializeIfNeeded_locked

    // 如果尚未实现,则实现给定的类;如果尚未初始化,则初始化该类。
    
    /***********************************************************************
    * realizeAndInitializeIfNeeded_locked
    * Realize the given class if not already realized, and initialize it if
    * not already initialized.
    * inst is an instance of cls or a subclass, or nil if none is known.
    * cls is the class to initialize and realize.
    * initializer is true to initialize the class, false to skip initialization.
    **********************************************************************/
    static Class
    realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize)
    {
        runtimeLock.assertLocked();
        // 如果类存在,没有实现,需要先实现,此时的目的是为了确定父类链,方法后续的循环
        if (slowpath(!cls->isRealized())) {
            cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
            // runtimeLock may have been dropped but is now locked again
        }
    
        
        if (slowpath(initialize && !cls->isInitialized())) {
            cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
            // runtimeLock may have been dropped but is now locked again
    
            // 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
        }
        return cls;
    }
    

    按顺序看下, 类没有实现会走这个方法realizeClassMaybeSwiftAndLeaveLocked
    先看下realizeClassMaybeSwiftAndLeaveLocked源码

    static Class
    realizeClassMaybeSwiftAndLeaveLocked(Class cls, mutex_t& lock)
    {
        return realizeClassMaybeSwiftMaybeRelock(cls, lock, true);
    }
    
    /***********************************************************************
    * realizeClassMaybeSwift (MaybeRelock / AndUnlock / AndLeaveLocked)
    * Realize a class that might be a Swift class.
    * Returns the real class structure for the class. 
    * Locking: 
    *   runtimeLock must be held on entry
    *   runtimeLock may be dropped during execution
    *   ...AndUnlock function leaves runtimeLock unlocked on exit
    *   ...AndLeaveLocked re-acquires runtimeLock if it was dropped
    * This complication avoids repeated lock transitions in some cases.
    **********************************************************************/
    static Class
    realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked)
    {
        lock.assertLocked();
    
        if (!cls->isSwiftStable_ButAllowLegacyForNow()) {
            // Non-Swift class. Realize it now with the lock still held.
            // fixme wrong in the future for objc subclasses of swift classes
    
            // 重点: 实现当前的类方法
            realizeClassWithoutSwift(cls, nil);
            if (!leaveLocked) lock.unlock();
        } else {
            // Swift class. We need to drop locks and call the Swift
            // runtime to initialize it.
            lock.unlock();
            cls = realizeSwiftClass(cls);
            ASSERT(cls->isRealized());    // callback must have provoked realization
            if (leaveLocked) lock.lock();
        }
    
        return cls;
    }
    
    
    /***********************************************************************
    * realizeClassWithoutSwift
    * Performs first-time initialization on class cls, 
    * including allocating its read-write data.
    * Does not perform any Swift-side initialization.
    * Returns the real class structure for the class. 
    * Locking: runtimeLock must be write-locked by the caller
    **********************************************************************/
    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?
    
      
        // 拿到class中的data信息
        // 其中ro 是clean memory (如果文件只读, 那么这部分内存就属于 clean memory)
        // 其中rw 是dirty memory
    
        // 普及些小知识点
        // clean memory 指的是能被重新创建的内存,它主要包含这几类:
        // 1.app 的二进制可执行文件
        // 2.framework 中的 _DATA_CONST 段
        // 3.文件映射的内存
        // 4.未写入数据的内存
        // 内存映射的文件指的是当 app 访问一个文件时,系统会将文件映射加载到内存中,如果文件只读,那么这部分内存就属于 clean memory。
        // 另外需要注意的是, framework 中 _DATA_CONST 并不绝对属于 clean memory, 当 app 使用到 framework 时, 就会变成 dirty memory。
        // 所有不属于 clean memory 的内存都是 dirty memory。
        //  dirty memory这部分内存并不能被系统重新创建, 所以 dirty memory 会始终占据物理内存, 直到物理内存不够用之后,系统便会开始清理。
        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, ro 即给 dirty memory 赋值
            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
    
        // 关联父类, 子类
        // 此时class是双向链表结构, 父子关系都确定
        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;
    }
    
     // methodizeClass 这个方法主要是, 把所有的cls中所有的 method list, protocol list, and property list. 贴到rwe 中
    /***********************************************************************
    * methodizeClass
    * Fixes up cls's method list, protocol list, and property list.
    * Attaches any outstanding categories.
    * Locking: runtimeLock must be held by the caller
    **********************************************************************/
    static void methodizeClass(Class cls, Class previously)
    {
        runtimeLock.assertLocked();
    
        bool isMeta = cls->isMetaClass();
        auto rw = cls->data();
        auto ro = rw->ro();
        auto rwe = rw->ext();
    
        // Methodizing for the first time
        if (PrintConnecting) {
            _objc_inform("CLASS: methodizing class '%s' %s", 
                         cls->nameForLogging(), isMeta ? "(meta)" : "");
        }
    
        // Install methods and properties that the class implements itself.
        method_list_t *list = ro->baseMethods();
        if (list) {
            prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
            if (rwe) rwe->methods.attachLists(&list, 1);
        }
    
        property_list_t *proplist = ro->baseProperties;
        if (rwe && proplist) {
            rwe->properties.attachLists(&proplist, 1);
        }
    
        protocol_list_t *protolist = ro->baseProtocols;
        if (rwe && protolist) {
            rwe->protocols.attachLists(&protolist, 1);
        }
    
        // Root classes get bonus method implementations if they don't have 
        // them already. These apply before category replacements.
        if (cls->isRootMetaclass()) {
            // root metaclass
            addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
        }
    
        // Attach categories.
        if (previously) {
            if (isMeta) {
                objc::unattachedCategories.attachToClass(cls, previously,
                                                         ATTACH_METACLASS);
            } else {
                // When a class relocates, categories with class methods
                // may be registered on the class itself rather than on
                // the metaclass. Tell attachToClass to look for those.
                objc::unattachedCategories.attachToClass(cls, previously,
                                                         ATTACH_CLASS_AND_METACLASS);
            }
        }
        objc::unattachedCategories.attachToClass(cls, cls,
                                                 isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
    
    #if DEBUG
        // Debug: sanity-check all SELs; log method list contents
        for (const auto& meth : rw->methods()) {
            if (PrintConnecting) {
                _objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-', 
                             cls->nameForLogging(), sel_getName(meth.name()));
            }
            ASSERT(sel_registerName(sel_getName(meth.name())) == meth.name());
        }
    #endif
    }
    
    

    接下来看下是当前类没有初始话, 走的方法initializeAndLeaveLocked, 主要就是一层层初始化所有类, 父类, 元类......

    // Locking: caller must hold runtimeLock; this may drop and re-acquire it
    static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock)
    {
        return initializeAndMaybeRelock(cls, obj, lock, true);
    }
    
    
    /***********************************************************************
    * class_initialize.  Send the '+initialize' message on demand to any
    * uninitialized class. Force initialization of superclasses first.
    * inst is an instance of cls, or nil. Non-nil is better for performance.
    * Returns the class pointer. If the class was unrealized then 
    * it may be reallocated.
    * Locking: 
    *   runtimeLock must be held by the caller
    *   This function may drop the lock.
    *   On exit the lock is re-acquired or dropped as requested by leaveLocked.
    **********************************************************************/
    static Class initializeAndMaybeRelock(Class cls, id inst,
                                          mutex_t& lock, bool leaveLocked)
    {
        lock.assertLocked();
        ASSERT(cls->isRealized());
    
        if (cls->isInitialized()) {
            if (!leaveLocked) lock.unlock();
            return cls;
        }
    
        // Find the non-meta class for cls, if it is not already one.
        // The +initialize message is sent to the non-meta class object.
        Class nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
    
        // Realize the non-meta class if necessary.
        if (nonmeta->isRealized()) {
            // nonmeta is cls, which was already realized
            // OR nonmeta is distinct, but is already realized
            // - nothing else to do
            lock.unlock();
        } else {
            nonmeta = realizeClassMaybeSwiftAndUnlock(nonmeta, lock);
            // runtimeLock is now unlocked
            // fixme Swift can't relocate the class today,
            // but someday it will:
            cls = object_getClass(nonmeta);
        }
    
        // runtimeLock is now unlocked, for +initialize dispatch
        ASSERT(nonmeta->isRealized());
    
        // 初始化元类
        initializeNonMetaClass(nonmeta);
    
        if (leaveLocked) runtimeLock.lock();
        return cls;
    }
    
    /***********************************************************************
    * class_initialize.  Send the '+initialize' message on demand to any
    * uninitialized class. Force initialization of superclasses first.
    **********************************************************************/
    void initializeNonMetaClass(Class cls)
    {
        ASSERT(!cls->isMetaClass());
    
        Class supercls;
        bool reallyInitialize = NO;
    
        // Make sure super is done initializing BEFORE beginning to initialize cls.
        // See note about deadlock above.
        supercls = cls->getSuperclass();
        if (supercls  &&  !supercls->isInitialized()) {
            initializeNonMetaClass(supercls);
        }
        
        // Try to atomically set CLS_INITIALIZING.
        SmallVector<_objc_willInitializeClassCallback, 1> localWillInitializeFuncs;
        {
            monitor_locker_t lock(classInitLock);
            if (!cls->isInitialized() && !cls->isInitializing()) {
                cls->setInitializing();
                reallyInitialize = YES;
    
                // Grab a copy of the will-initialize funcs with the lock held.
                localWillInitializeFuncs.initFrom(willInitializeFuncs);
            }
        }
        
        if (reallyInitialize) {
            // We successfully set the CLS_INITIALIZING bit. Initialize the class.
            
            // Record that we're initializing this class so we can message it.
            _setThisThreadIsInitializingClass(cls);
    
            if (MultithreadedForkChild) {
                // LOL JK we don't really call +initialize methods after fork().
                performForkChildInitialize(cls, supercls);
                return;
            }
            
            for (auto callback : localWillInitializeFuncs)
                callback.f(callback.context, cls);
    
            // Send the +initialize message.
            // Note that +initialize is sent to the superclass (again) if 
            // this class doesn't implement +initialize. 2157218
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: calling +[%s initialize]",
                             objc_thread_self(), cls->nameForLogging());
            }
    
            // Exceptions: A +initialize call that throws an exception 
            // is deemed to be a complete and successful +initialize.
            //
            // Only __OBJC2__ adds these handlers. !__OBJC2__ has a
            // bootstrapping problem of this versus CF's call to
            // objc_exception_set_functions().
    #if __OBJC2__
            @try
    #endif
            {
                callInitialize(cls);
    
                if (PrintInitializing) {
                    _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
                                 objc_thread_self(), cls->nameForLogging());
                }
            }
    #if __OBJC2__
            @catch (...) {
                if (PrintInitializing) {
                    _objc_inform("INITIALIZE: thread %p: +[%s initialize] "
                                 "threw an exception",
                                 objc_thread_self(), cls->nameForLogging());
                }
                @throw;
            }
            @finally
    #endif
            {
                // Done initializing.
                lockAndFinishInitializing(cls, supercls);
            }
            return;
        }
        
        else if (cls->isInitializing()) {
            // We couldn't set INITIALIZING because INITIALIZING was already set.
            // If this thread set it earlier, continue normally.
            // If some other thread set it, block until initialize is done.
            // It's ok if INITIALIZING changes to INITIALIZED while we're here, 
            //   because we safely check for INITIALIZED inside the lock 
            //   before blocking.
            if (_thisThreadIsInitializingClass(cls)) {
                return;
            } else if (!MultithreadedForkChild) {
                waitForInitializeToComplete(cls);
                return;
            } else {
                // We're on the child side of fork(), facing a class that
                // was initializing by some other thread when fork() was called.
                _setThisThreadIsInitializingClass(cls);
                performForkChildInitialize(cls, supercls);
            }
        }
        
        else if (cls->isInitialized()) {
            // Set CLS_INITIALIZING failed because someone else already 
            //   initialized the class. Continue normally.
            // NOTE this check must come AFTER the ISINITIALIZING case.
            // Otherwise: Another thread is initializing this class. ISINITIALIZED 
            //   is false. Skip this clause. Then the other thread finishes 
            //   initialization and sets INITIALIZING=no and INITIALIZED=yes. 
            //   Skip the ISINITIALIZING clause. Die horribly.
            return;
        }
        
        else {
            // We shouldn't be here. 
            _objc_fatal("thread-safe class init in objc runtime is buggy!");
        }
    }
    
    // 向类里面的 initialize 发送消息, 就是自动调用类中 initialize方法, 比如类中自定义initialize, 调用时候回打印123, 其实跟 load 方法类似, 系统默认为我们调用
    // + (void)initialize {
    //     NSLog(@"123");
    // }
    
    void callInitialize(Class cls)
    {
        ((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
        asm("");
    }
    

    详细分析四: getMethodNoSuper_nolock

    这个方法其实就是clsdatamethods取到methods, 然后二分查找,核心是方法是findMethodInSortedMethodList, 源码下面也有2个例子便于理解。

    /***********************************************************************
     * getMethodNoSuper_nolock
     * fixme
     * Locking: runtimeLock must be read- or write-locked by the caller
     **********************************************************************/
    static method_t *
    getMethodNoSuper_nolock(Class cls, SEL sel)
    {
        runtimeLock.assertLocked();
    
        ASSERT(cls->isRealized());
        // fixme nil cls? 
        // fixme nil sel?
    
        auto const methods = cls->data()->methods();
        for (auto mlists = methods.beginLists(),
                  end = methods.endLists();
             mlists != end;
             ++mlists)
        {
            // <rdar://problem/46904873> getMethodNoSuper_nolock is the hottest
            // caller of search_method_list, inlining it turns
            // getMethodNoSuper_nolock into a frame-less function and eliminates
            // any store from this codepath.
            method_t *m = search_method_list_inline(*mlists, sel);
            if (m) return m;
        }
    
        return nil;
    }
    
    
    ALWAYS_INLINE static method_t *
    search_method_list_inline(const method_list_t *mlist, SEL sel)
    {
        int methodListIsFixedUp = mlist->isFixedUp();
        int methodListHasExpectedSize = mlist->isExpectedSize();
        
        if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) {
            return findMethodInSortedMethodList(sel, mlist);
        } else {
            // Linear search of unsorted method list
            if (auto *m = findMethodInUnsortedMethodList(sel, mlist))
                return m;
        }
    
    #if DEBUG
        // sanity-check negative results
        if (mlist->isFixedUp()) {
            for (auto& meth : *mlist) {
                if (meth.name() == sel) {
                    _objc_fatal("linear search worked when binary search did not");
                }
            }
        }
    #endif
    
        return nil;
    }
    
    
    // 二分查找核心方法
    /***********************************************************************
     * search_method_list_inline
     **********************************************************************/
    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;
        
        // base 为初始下标, count为个数, probe是中间二分下标 不断的递归二分实现找到值
        // 下边有例子 
        for (count = list->count; count != 0; count >>= 1) {
            //从首地址+下标 --> 移动到中间位置(count >> 1 右移1位即 count/2 = 4)
            probe = base + (count >> 1);
            
            uintptr_t probeValue = (uintptr_t)getName(probe);
            
           //如果查找的key的keyvalue刚好等于中间位置(probe)的probeValue,则直接返回中间位置
            if (keyValue == probeValue) {
                // `probe` is a match.
                // Rewind looking for the *first* occurrence of this value.
                // This is required for correct category overrides.
    
    
               //  这里是做的while是, 排除分类重名
               //  判断 当前我已经找到的方法, 发现前面有和他一样的方法, 即分类
                while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
                    // 分类永远在主类前面
                    // 排除分类重名方法(由于方法的存储是先存储类方法,在存储分类---按照先进后出的原则,分类方法最先出,而我们要取的类方法,所以需要先排除分类方法)
                    // 如果是两个分类,就看谁先进行加载
                    probe--;
                }
                return &*probe;
            }
            
            // 不匹配, 如果keyValue 大于 probeValue
            // base = probe + 1 就往probe即中间位置的右边查找
            if (keyValue > probeValue) {
                base = probe + 1;
                count--;
            }
        }
        
        return nil;
    }
    
    二分查找例子:

    例子1: 一个类里面有8个方法: 01, 02, 03, 04, 05, 06, 07, 08

    base = first = 01, count = 8, 比如我们想找第2个, 进行循环

    • 第一次循环
      probe = 01 + 8 >>1 = 01 右移 4位 = 05 ( count >> 1右移1, 8 (1000)右移1位变为4 (0100) )

      • 此时02 < 05
      • keyValue == probeValue: 不满足
      • keyvalue > probeValue: 不满足
    • 第二次循环

      • base 不变还为01, 但count, 进行了一次判断循环, 现在为4
        probe = 01 + 4>>1 = 03
      • 此时02 < 03
      • keyValue == probeValue: 不满足
      • keyvalue > probeValue: 不满足
    • 第三次循环

      • base 不变还为01, 但count, 进行了一次判断循环, 现在为4
        probe = 01 + 2>>1 = 02
      • 此时02 == 02
      • keyValue == probeValue: 满足, 判断下是否有分类重名, 已找到结束循环返回



    例子2: 还是之前例子, 我们想找第7个
    base = first = 01, count = 8, 比如我们想找第2个, 进行循环

    • 第一次循环
      probe = 01 + 8 >>1 = 01 右移 4位 = 05 ( count >> 1右移1, 8 (1000)右移1位变为4 (0100) )

      • 此时07 > 05
      • keyValue == probeValue: 不满足
      • keyvalue > probeValue: 满足, base = 05 + 1 = 06, count = 7
    • 第二次循环

      • base 不变还为01, 但count = 7, 右移1位, 变3
        probe = 06 + 3>>1 = 067
      • 此时07 == 07
      • keyValue == probeValue: 满足判断下是否有分类重名, 已找到结束循环返回

    详细分析五: cache_getImp

    // cache_getImp方法是通过汇编_cache_getImp实现
    // 如果父类缓存中找到了方法实现,则跳转至CacheHit即命中,则直接返回imp
    // 如果在父类缓存中,没有找到方法实现,则跳转Miss
    // 这里普及个知识点, 跟缓存相关的就是汇编, 因为汇编比C/C++快
        STATIC_ENTRY _cache_getImp
    
        GetClassFromIsa_p16 p0, 0
    
            // 去执行 CacheLookup 方法, 里面传4个参数
        CacheLookup GETIMP, _cache_getImp, LGetImpMissDynamic, LGetImpMissConstant
    
    LGetImpMissDynamic:
        mov p0, #0
        ret
    
    LGetImpMissConstant:
        mov p0, p2
        ret
    
        END_ENTRY _cache_getImp
    
    
    
    
    
    .macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
        //
        // Restart protocol:
        //
        //   As soon as we're past the LLookupStart\Function label we may have
        //   loaded an invalid cache pointer or mask.
        //
        //   When task_restartable_ranges_synchronize() is called,
        //   (or when a signal hits us) before we're past LLookupEnd\Function,
        //   then our PC will be reset to LLookupRecover\Function which forcefully
        //   jumps to the cache-miss codepath which have the following
        //   requirements:
        //
        //   GETIMP:
        //     The cache-miss is just returning NULL (setting x0 to 0)
        //
        //   NORMAL and LOOKUP:
        //   - x0 contains the receiver
        //   - x1 contains the selector
        //   - x16 contains the isa
        //   - other registers are set as per calling conventions
        //
    
        mov x15, x16            // stash the original isa
    LLookupStart\Function:
        // p1 = SEL, p16 = isa
    #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
        ldr p10, [x16, #CACHE]              // p10 = mask|buckets
        lsr p11, p10, #48           // p11 = mask
        and p10, p10, #0xffffffffffff   // p10 = buckets
        and w12, w1, w11            // x12 = _cmd & mask
    #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
        ldr p11, [x16, #CACHE]          // p11 = mask|buckets
    #if CONFIG_USE_PREOPT_CACHES
    #if __has_feature(ptrauth_calls)
        tbnz    p11, #0, LLookupPreopt\Function
        and p10, p11, #0x0000ffffffffffff   // p10 = buckets
    #else
        and p10, p11, #0x0000fffffffffffe   // p10 = buckets
        tbnz    p11, #0, LLookupPreopt\Function
    #endif
        eor p12, p1, p1, LSR #7
        and p12, p12, p11, LSR #48      // x12 = (_cmd ^ (_cmd >> 7)) & mask
    #else
        and p10, p11, #0x0000ffffffffffff   // p10 = buckets
        and p12, p1, p11, LSR #48       // x12 = _cmd & mask
    #endif // CONFIG_USE_PREOPT_CACHES
    #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
        ldr p11, [x16, #CACHE]              // p11 = mask|buckets
        and p10, p11, #~0xf         // p10 = buckets
        and p11, p11, #0xf          // p11 = maskShift
        mov p12, #0xffff
        lsr p11, p12, p11           // p11 = mask = 0xffff >> p11
        and p12, p1, p11            // x12 = _cmd & mask
    #else
    #error Unsupported cache mask storage for ARM64.
    #endif
    
        add p13, p10, p12, LSL #(1+PTRSHIFT)
                            // p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
    
                            // do {
    1:  ldp p17, p9, [x13], #-BUCKET_SIZE   //     {imp, sel} = *bucket--
        cmp p9, p1              //     if (sel != _cmd) {
        b.ne    3f              //         scan more
                            //     } else {
    2:  CacheHit \Mode              // hit:    call or return imp
                            //     }
    3:  cbz p9, \MissLabelDynamic       //     if (sel == 0) goto Miss;
        cmp p13, p10            // } while (bucket >= buckets)
        b.hs    1b
    
        // wrap-around:
        //   p10 = first bucket
        //   p11 = mask (and maybe other bits on LP64)
        //   p12 = _cmd & mask
        //
        // A full cache can happen with CACHE_ALLOW_FULL_UTILIZATION.
        // So stop when we circle back to the first probed bucket
        // rather than when hitting the first bucket again.
        //
        // Note that we might probe the initial bucket twice
        // when the first probed slot is the last entry.
    
    
    #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
        add p13, p10, w11, UXTW #(1+PTRSHIFT)
                            // p13 = buckets + (mask << 1+PTRSHIFT)
    #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
        add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
                            // p13 = buckets + (mask << 1+PTRSHIFT)
                            // see comment about maskZeroBits
    #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
        add p13, p10, p11, LSL #(1+PTRSHIFT)
                            // p13 = buckets + (mask << 1+PTRSHIFT)
    #else
    #error Unsupported cache mask storage for ARM64.
    #endif
        add p12, p10, p12, LSL #(1+PTRSHIFT)
                            // p12 = first probed bucket
    
                            // do {
    4:  ldp p17, p9, [x13], #-BUCKET_SIZE   //     {imp, sel} = *bucket--
        cmp p9, p1              //     if (sel == _cmd)
        b.eq    2b              //         goto hit
        cmp p9, #0              // } while (sel != 0 &&
        ccmp    p13, p12, #0, ne        //     bucket > first_probed)
        b.hi    4b
    
    LLookupEnd\Function:
    LLookupRecover\Function:
        b   \MissLabelDynamic
    
    #if CONFIG_USE_PREOPT_CACHES
    #if CACHE_MASK_STORAGE != CACHE_MASK_STORAGE_HIGH_16
    #error config unsupported
    #endif
    LLookupPreopt\Function:
    #if __has_feature(ptrauth_calls)
        and p10, p11, #0x007ffffffffffffe   // p10 = buckets
        autdb   x10, x16            // auth as early as possible
    #endif
    
        // x12 = (_cmd - first_shared_cache_sel)
        adrp    x9, _MagicSelRef@PAGE
        ldr p9, [x9, _MagicSelRef@PAGEOFF]
        sub p12, p1, p9
    
        // w9  = ((_cmd - first_shared_cache_sel) >> hash_shift & hash_mask)
    #if __has_feature(ptrauth_calls)
        // bits 63..60 of x11 are the number of bits in hash_mask
        // bits 59..55 of x11 is hash_shift
    
        lsr x17, x11, #55           // w17 = (hash_shift, ...)
        lsr w9, w12, w17            // >>= shift
    
        lsr x17, x11, #60           // w17 = mask_bits
        mov x11, #0x7fff
        lsr x11, x11, x17           // p11 = mask (0x7fff >> mask_bits)
        and x9, x9, x11         // &= mask
    #else
        // bits 63..53 of x11 is hash_mask
        // bits 52..48 of x11 is hash_shift
        lsr x17, x11, #48           // w17 = (hash_shift, hash_mask)
        lsr w9, w12, w17            // >>= shift
        and x9, x9, x11, LSR #53        // &=  mask
    #endif
    
        ldr x17, [x10, x9, LSL #3]      // x17 == sel_offs | (imp_offs << 32)
        cmp x12, w17, uxtw
    
     // 这段是关键, 当Mode == GETIMP,返回cache miss, 即MissLabelConstant
    .if \Mode == GETIMP
        b.ne    \MissLabelConstant      // cache miss
        sub x0, x16, x17, LSR #32       // imp = isa - imp_offs
        SignAsImp x0
        ret
    .else
        b.ne    5f              // cache miss
        sub x17, x16, x17, LSR #32      // imp = isa - imp_offs
    .if \Mode == NORMAL
        br  x17
    .elseif \Mode == LOOKUP
        orr x16, x16, #3 // for instrumentation, note that we hit a constant cache
        SignAsImp x17
        ret
    .else
    .abort  unhandled mode \Mode
    .endif
    
    5:  ldursw  x9, [x10, #-8]          // offset -8 is the fallback offset
        add x16, x16, x9            // compute the fallback isa
        b   LLookupStart\Function       // lookup again with a new isa
    .endif
    #endif // CONFIG_USE_PREOPT_CACHES
    
    .endmacro
    
    
    • 进入_cache_getImp, 可看到内部执行调用 CacheLookup 方法, 里面传4个参数, 需要留意第一个参数GETIMP
    • 关键代码.if \Mode == GETIMP b.ne \MissLabelConstant // cache miss, 当Mode == GETIMP, 返回MissLabelConstant 即返回走 LGetImpMissConstant 方法
    • LGetImpMissConstant: mov p0, p2 ret, 这里可看到令p0 = p2(即将p2存到寄存器p0), ret = return返回
    • _cache_getImp无递归, 只是依次查找父类的缓存是否有指定方法, 直到NSObject父类nil

    总结

    慢速查找流程

    针对于快速查找没有找到走慢速查找流程

    • 对于实例方法,即在中查找,对应慢速查找父类链为:根类nil

    • 对于类方法,即在元类中查找,对应慢速查找父类链为:元类根元类根类nil

    • 如果慢速查找也没有找到,则尝试动态方法决议

    • 如果动态方法决议仍然没有找到,则进行消息转发

    • 如果都没有找到就imp = forward_imp走经典carsh方法

    +[SAStudent sayGunDan]: unrecognized selector sent to class XXX
     Terminating app due to uncaught exception 'NSInvalidArgumentException', 
    reason: '+[SAStudent sayGunDan]: unrecognized selector sent to class XXX'
    

    相关文章

      网友评论

          本文标题:IOS底层(十四): 消息流程(二)慢速查找

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