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

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();
        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)
    {
        //  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
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底层(十四): 消息流程(二)慢速查找)