Objc4-818底层探索(九):msgSend消息发送(二): 慢速查找

当快速查找流程找不到方法时候, 走goto Miss 方法

__objc_msgSend_uncached

    // 汇编
    STATIC_ENTRY __objc_msgSend_uncached  // 进入__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
  • 先看下TailCallFunctionPointer方法
.macro TailCallFunctionPointer
    // $0 = function pointer value 函数指针
    br  $0
.endmacro

可看出, 这里实际上是调用函数指针方法, 说明如果走到这里, 函数的imp已经找到。

那么 MethodTableLookup 这个方法即是找到imp方法, 看下源码

.macro MethodTableLookup
    
    SAVE_REGS MSGSEND // 注册的消息

    // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
    // receiver and selector already in x0 and x1
        // 接受者和方法已经存在 x0 和 x1 中
    mov x2, x16  // mov:传送指令, 令x2 = x16
    mov x3, #3    // 令x3 = #3
    bl  _lookUpImpOrForward // bl:跳转指令, 跳转_lookUpImpOrForward方法

    // IMP in x0
    mov x17, x0  // 令x17 = x0此时x0为imp

    RESTORE_REGS MSGSEND //返回注册消息

.endmacro
汇编流程

可看出关键代码是_lookUpImpOrForward做了些操作, 查到了imp, 由于是一个下划线_, 那么我们直接在底层代码查找即可。这里普及个知识点

关于下划线知识点

  • 1、C/C++调用汇编:   查找汇编时,C/C++调用的方法需要多加一个下划线( ___ )
  • 2、汇编 调用 C/C++方法:  查找C/C++方法,需要将汇编调用的方法去掉一个下划线( ___ )


接下来我们代码验证下

  • 普通工程里面建一个类继承NSObject, 里面有一个对象方法sayHello, ViewController调用一下
调用方法
  • 打开汇编Debug → Debug worlflow → Always show Disassembly
打开汇编

可以看到走了objc_msgSend方法

  • control + Step into, 进入objc_msgSend

    control + Step into

  • 因为有很多方法都会走消息发送, 在这块我们确保进入的是sayHello的, 在lldbregister read读取寄存器一下

    register read

当然我们也可以直接读取下x1register read x1

register read x1

继续往后走


objc_msgSend

objc_msgSend方法中我们发现往后进入了 _objc_msgSend_uncached方法

objc_msgSend

objc_msgSend
objc_msgSend
objc_msgSend

断点跟流程, 我们会发现还走了一个class_lookupMethodAndLoadCache3 , 之后才是lookUpImpOrForward, iOS系统旧版本才会有这种情况, 新版本请直接略过
我们可以在objc4-750找到这个方法(之后版本已取消这个方法)

/***********************************************************************
* _class_lookupMethodAndLoadCache.
* Method lookup for dispatchers ONLY. OTHER CODE SHOULD USE lookUpImp().
* This lookup avoids optimistic cache scan because the dispatcher 
* already tried that.
**********************************************************************/
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

可看出是一个承上启下方法, 实际上也是调用lookUpImpOrForward

objc_msgSend

汇编继续往后走, 也会发现也会走到lookUpImpOrForward方法

不过留意下 class_lookupMethodAndLoadCache3objc-750之后就取消了, ios新系统版本_objc_msgSend_uncached中直接走lookUpImpOrForward(下面为ios14.6测试图片)

image.png



lookUpImpOrForward

接下来我们看下慢速查找lookUpImpOrForward方法

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;
...
    checkIsKnownClass(cls);

    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
    runtimeLock.assertLocked();
    curClass = cls;

    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)) { 
                imp = forward_imp;
                break;
            }
        }
  ...
    }

...
    return imp;
}

可能有些人会有疑问? 为什么缓存用的是汇编?

  1. 首先缓存查找目的为了效率高, 恰巧汇编语言执行起来非常迅速/快, 同时汇编处理相对来说安全一下。

  2. 方法在下层尤其多几W, 几十W, 用汇编可以快速优化时间

3.针对参数不明确情况(C/C++方法需要参数明确), 汇编可以更加动态化的处理

而慢速查找需要不断遍历/循环 都是一些耗时操作, 所以用C++/C相对好一些, 灵活。

返回lookUpImpOrForward代码比较多, 这里我分几篇文章, 拆分模块看。(总结在第二篇)

checkIsKnownClass

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

/***********************************************************************
* checkIsKnownClass
* Checks the given class against the list of all known classes. Dies
* with a fatal error if the class is not known.
* Locking: runtimeLock must be held by the caller.
**********************************************************************/
ALWAYS_INLINE
static void
checkIsKnownClass(Class cls)
{
    // 快速判断如果类未知报错返回, 类已知则继续
    if (slowpath(!isKnownClass(cls))) {
        _objc_fatal("Attempt to use unknown class %p.", cls);
    }
}

这个方法是, 判断当前的类是否已经注册到缓存表里面, 即 注册类。如果当前类未知, 直接返回错误, Attempt to use unknown class XXXX ....



realizeAndInitializeIfNeeded_locked

cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
curClass = cls;
/***********************************************************************
* 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())) {
        // 当前类如果没实现, 调用realizeClassMaybeSwiftAndLeaveLocked, 先实现一下
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
        // runtimeLock may have been dropped but is now locked again
    }
  
   // 判断类有没有初始化
    if (slowpath(initialize && !cls->isInitialized())) {
        // 当前类如果没实现, 调用initializeAndLeaveLocked, 先初始化一下
        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

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 (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);
    }
...
    supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
...

    // Update superclass and metaclass in case of remapping
    cls->setSuperclass(supercls);
    cls->initClassIsa(metacls);

可看到主要是先对rw, ro的一些数据处理, 之后处理了superclsmetacls

  • supercls: cls->getSuperclass()递归取父类, 再调用自身方法realizeClassWithoutSwift, 即确定父类信息。

  • metacls: cls->ISA()递归取元类, 再调用自身方法realizeClassWithoutSwift, 即确定父类信息。

之后调用cls->setSuperclass(supercls), cls->initClassIsa(metacls)将当前类的父类链, 元类链确认绑定。

isa流程图.png

即知道一个, 变将父类/元类所有都初始化完, 方便之后找方法。其实为了

  • 对象方法: 当前没有去父类找, 父类没有再去根类找...
  • 类方法: 当前元类没有去父元类找, 父元类没有再去根元类找...


二分查找

继续往后面走来到for (unsigned attempts = unreasonableClassCount();;), 一个死循环, 只可以通过break, 或return结束循环

    // 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();;) {
        
         // 判断curClass是否被缓存, 防止因为上面不断调用ro, rw被对应方法被缓存, 如果当前类可以根据 sel找到imp, 即走 if 判断, 
         // 不过通常是没有的
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES  // IOS真机为1, 其他为0

 // #if defined(__arm64__) && TARGET_OS_IOS && !TARGET_OS_SIMULATOR && !TARGET_OS_MACCATALYST
 //  #define CONFIG_USE_PREOPT_CACHES 1
 //  #else
 //  #define CONFIG_USE_PREOPT_CACHES 0
 //  #endif

            // 如果缓存能找到对应的imp, 直接跳转 goto done_unlock 方法
            imp = cache_getImp(curClass, sel);
            if (imp) goto done_unlock;
            curClass = curClass->cache.preoptFallbackClass();
#endif
        } else {
            // curClass method list.
            // 如果缓存没有找到对应的imp, 走二分查找
            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;
            }
        }
/***********************************************************************
 * getMethodNoSuper_nolock
 * fixme
 * Locking: runtimeLock must be read- or write-locked by the caller
 **********************************************************************/
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
    ...
    // 从类中获取方法列表methods
    auto const methods = cls->data()->methods();
    // 循环二位数组
    for (auto mlists = methods.beginLists(),
              end = methods.endLists();
         mlists != end;
         ++mlists)
    {
     ...
        // 从方法中查找
        method_t *m = search_method_list_inline(*mlists, sel);
        if (m) return m;
    }

    return nil;
}

调用search_method_list_inline继续查找

ALWAYS_INLINE static method_t *
search_method_list_inline(const method_list_t *mlist, SEL sel)
{
    // 检查mlist 是否固定顺序
    int methodListIsFixedUp = mlist->isFixedUp();
    // 检查mlist 是否预期大小
    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;
    }
...
    return nil;
}

这里我们可以读一下methodListIsFixedUpmethodListHasExpectedSize打断点可知走的是有序方法findMethodInSortedMethodList

但是他有2个方法, 也是打断点跟一下。

findMethodInSortedMethodList

可以看出, 先走的是判断是否为SmallList。这里其实是做了一个M1机器和普通Mac机器的区分, 普通Mac机直接走下面 findMethodInSortedMethodList二分查找方法

findMethodInSortedMethodList

findMethodInSortedMethodList

!!! 重点方法二分查找

/***********************************************************************
 * 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(); // 初始化first为list首位
    auto base = first;  // 定义 base为 first
    decltype(first) probe; // 定义循环查的位置 probe
    
    uintptr_t keyValue = (uintptr_t)key; // 定义keyValue为我们查找的sel
    uint32_t count; //定义count
    
    // 循环
    // count 为 list 元素个数
    // 循环条件: count != 0
    // 每次count >>= 1, 除2操作, 二分方法关键循环不断 / 2
    for (count = list->count; count != 0; count >>= 1) {

        //  probe 为 base + count再除2操作
        probe = base + (count >> 1);

        //  定义probeValue 为查询sel
        uintptr_t probeValue = (uintptr_t)getName(probe);
        
        //  如果当前查询sel == 循环sel
        if (keyValue == probeValue) {
            // 做一步分类重名排查处理
            while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
                probe--;
            }
            // 返回 probe值
            return &*probe;
        }
        
        // 如果keyValue > probeValue 做一步 `base = probe + 1;  count--;`继续循环
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    } 
    return nil;
}

这块我们, 结合例子来处理

例如: count = 8

  • 进入for循环, 循环条件count != 0, count >>= 1即除2处理,
    例如①: 8 = 1000 → 右移1位 → 0100 = 4
    例如②: 7 = 0111 → 右移1位 → 0011 = 3

  • probe = 0 + 4 >> 1 = 0 + 2 = 2

  • 判断循环方法是否和查找方法一致, keyValue == probeValue如果满足, 即方法找到 , 做一步防止分类重名处理,

           //  这里是做的while是, 排除分类重名
           //  判断 当前我已经找到的方法, 前面是否有和他一样的方法或者说同名方法(分类方法, 分类是加载到原始类前面的)
            while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
                // 分类永远在主类前面
                // 排除分类重名方法(由于方法的存储是先存储类方法,再存储分类---按照先进后出的原则,分类方法最先出,而我们要取的类方法,所以需要先排除分类方法)
                // 如果是两个分类,就看谁先进行加载
                probe--;
            }
return &*probe;
  • 如果没找到 判断keyValue > probeValue

    • : 做一步 base = probe + 1; count--;继续循环
    • : 继续循环
二分流程图

例子1

结合一个具体例子查看, 例如: list count为8, 我们想要查找是7, 则有

第一次循环:

  • count = 8, base = 0, probe = 0 + 8 >> 1 = 4

  • 判断 7 != 4, 走else

  • 因为 7 > 4, 走if, base = 4 + 1 = 5, count = 8 - 1 = 7

第二次循环:

  • count = 7 >> 1 = 3, base = 8, probe = 5 + 3 >> 1 = 6

  • 判断 7 != 6, 走else

  • 因为 7 > 6, 走if, base = 6 + 1 = 7, count = 3 - 1 = 2

第三次循环:

  • count = 2 >> 1 = 0, base = 7, probe = 7 + 0 >> 1 = 7

  • 判断 7 == 7, 走if判断, 进行分类排除, return


例子2

例如: list count为8, 我们想要查找是2, 则有

第一次循环:

  • count = 8, base = 0, probe = 0 + 8 >> 1 = 4

  • 判断 2 != 4, 走else

  • 因为 2 > 4, 走else, 继续循环

第二次循环:

  • count = 8 >> 1 = 4, base = 0, probe = 0 + 4 >> 1 = 2

  • 判断 2 == 2, 走if判断, 进行分类排除, return



done

            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                imp = meth->imp(false);
                goto done;
            }

如果 二分查找找到imp, 则走goto done;, 看一下done方法

enum {
    LOOKUP_INITIALIZE = 1,
    LOOKUP_RESOLVER = 2,
    LOOKUP_NIL = 4,
    LOOKUP_NOCACHE = 8,
};

 done:
    // 如果当前方法没缓存
    if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES  // 64位为1, 其他为0
        // 真机进行一个初始化缓存 cache_t 的操作
        while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
            cls = cls->cache.preoptFallbackClass();
        }
#endif
        // 针对已经查找到imp直接, 调用缓存插入方法, 下次就不进慢速, 直接快速
        log_and_fill_cache(cls, imp, sel, inst, curClass);
    }

真机底层调用preopt_cache_t

// 真机初始化
/* dyld_shared_cache_builder and obj-C agree on these definitions */
struct preopt_cache_t {
    int32_t  fallback_class_offset;
    union {
        struct {
            uint16_t shift       :  5;
            uint16_t mask        : 11;
        };
        uint16_t hash_params;
    };
    uint16_t occupied    : 14;
    uint16_t has_inlines :  1;
    uint16_t bit_one     :  1;
    preopt_cache_entry_t entries[];

    inline int capacity() const {
        return mask + 1;
    }
};

!!! 关键方法 log_and_fill_cache 缓存插入方法

/***********************************************************************
* log_and_fill_cache
* Log this method call. If the logger permits it, fill the method cache.
* cls is the method whose cache should be filled. 
* implementer is the class that owns the implementation in question.
**********************************************************************/
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if !TARGET_OS_OSX
#   define SUPPORT_MESSAGE_LOGGING 0
#else
#   define SUPPORT_MESSAGE_LOGGING 1
#endif
bool objcMsgLogEnabled = false; // 初始为false
#if SUPPORT_MESSAGE_LOGGING
    //  objcMsgLogEnabled默认为false
    // 只有调用void instrumentObjcMessageSends(BOOL flag)这方法才有机会将objcMsgLogEnabled设置为true
    // 一般跳过这里直接走 cache.insert
    if (slowpath(objcMsgLogEnabled && implementer)) {
        // 主要做一些写日志操作
      
        bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                      cls->nameForLogging(),
                                      implementer->nameForLogging(), 
                                      sel);
        if (!cacheIt) return;
    }
#endif
    // 缓存插入 imp, 下次直接cache快速查找
    cls->cache.insert(sel, imp, receiver);
}

我们也可以稍微看下logMessageSend方法, 主要是对消息日志做些写入操作

bool objcMsgLogEnabled = false; // 初始为false
static int objcMsgLogFD = -1; // 初始化-1

bool logMessageSend(bool isClassMethod,
                    const char *objectsClass,
                    const char *implementingClass,
                    SEL selector)
{
    char    buf[ 1024 ];

    // Create/open the log file
    if (objcMsgLogFD == (-1)) 
    {
        // 将一些入调用方法等其他信息写在 XXXX/tmp/msgSends-XXX内
        snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ());
        objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid());
        if (objcMsgLogFD < 0) {
            // no log file - disable logging
            objcMsgLogEnabled = false;
            objcMsgLogFD = -1;
            return true;
        }
    }
...
}

那么我们可以得出一个消息查找一个闭环


消息查找闭环
  • 第一次cache消息快速查找没有查找到, 进入消息慢速查找

  • 消息慢速查找 进行确认父类/元类链之后, 进行二分查找, 查找方法(imp)

  • 二分找到之后, 进行缓存插入操作, 下一次就直接快速查找, 不再走慢速



cache_getImp

当前类如果没有找到方法时候, 我们知道系统通常会去父类查找。但是源码是怎么操作的呢? 我们看一下

            // 当前类没找到, 将curClass为curClass父类继续查找
            if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
                // No implementation found, and method resolver didn't help.
                // Use forwarding.

                // 如果父类也没找到令imp为forward_imp, 继续查找
                imp = forward_imp;
                break;
            }
...
        // Superclass cache.
        // 调用cache_getImp方法
        imp = cache_getImp(curClass, sel);

这里的话是调用了一个cache_getImp, cache_getimp是全局搜索一下可发现是在汇编里面, 有

cache_getImp

可看出重新调用CacheLookup方法, 这里要留意下

.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
...
cbz p9, \MissLabelDynamic       //     if (sel == 0) goto Miss;
...

这里要留意下父类如果没找到会调用一个MissLabelDynamic 方法, MissLabelDynamic是传入的方法, 我们回头要看传入方法, 有:

CacheLookup GETIMP, _cache_getImp, LGetImpMissDynamic, LGetImpMissConstant

// LGetImpMissDynamic 方法
LGetImpMissDynamic:
    mov p0, #0  // 令p0 = 0, 返回空出去
    ret         // 返回

这里留意下查询父类, 不会走uncache流程。这里循环, 如果父类imp没找到就nil返回, 即imp = cache_getImp(curClass, sel);返回nil缓存没有查找到, 走for (unsigned attempts = unreasonableClassCount();;)再次循环。

此时curClass 为父类, 那么继续往下走。Method meth = getMethodNoSuper_nolock(curClass, sel);这个里再进行, 查找父类的方法列表 (内部调用cls->data()->methods())

顺序 父类快速查找→ 父类慢速 → 根类快速 → 根类慢速→ 直到 nil(但是实际上顺序是: 父类快速查找二次循环父类慢速→...中间其实有个再循环操作 )

实例方法父类链查找流程图


总结

  • cache快速查找方法没有找到走lookUpImpOrForward慢速查找方法

  • lookUpImpOrForward中先确认父类/元类链, 再通过二分查找, 查找当前类的方法列表

    • 找到: 方法存入缓存, 第二次直接走缓存快速查找

    • 没找到: 去父类查找, 顺序照旧, 先快速再慢速, 有存缓存, 无继续找父类

如果父类/元类链都查找完, 还是没有, 会走动态方法决议, 这个我放在下一篇文章来写

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容