[OC学习笔记]objc_msgSend(二):方法慢速查找

24 篇文章 1 订阅

一、执行慢速查找的时机

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

(一)__objc_msgSend_uncached

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

接下来先看一下TailCallFunctionPointer方法:

.macro TailCallFunctionPointer
	// $0 = function pointer value
	br	$0
.endmacro

可看出,这里实际上是调用函数指针方法,说明如果走到这里,函数的imp已经找到。那么 MethodTableLookup 这个方法应该就是找到imp方法,看下源码:

(二)MethodTableLookup

//Locate the implementation for a selector in a class's method lists.
//在类的方法列表中找到选择器的实现。
//参数:
//	$0 = NORMAL, STRET
//	r0 or r1 (STRET) = receiver
//	r1 or r2 (STRET) = selector
//	r9 = class to search in
//结束:
//  IMP in r12, eq/ne set for forwarding
.macro MethodTableLookup
	
	SAVE_REGS

	// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
	// 我们可以在objc-runtime-new.s找到它的实现
.if $0 == NORMAL
	// receiver already in r0
	// selector already in r1
.else
	mov 	r0, r1			// receiver
	mov 	r1, r2			// selector
.endif
	mov	r2, r9			// class to search
	mov	r3, #3			// LOOKUP_INITIALIZE | LOOKUP_RESOLVER
	blx	_lookUpImpOrForward   //⚠️跳转,cache里找不到,跳转到方法列表里查找(blx:一种汇编的跳转指令![请添加图片描述](https://img-blog.csdnimg.cn/a49ef1601b34432686e5425721f1a2a8.png)
)
	mov	r12, r0			// r12 = IMP
	
.if $0 == NORMAL
	cmp	r12, r12		// set eq for nonstret forwarding
.else
	tst	r12, r12		// set ne for stret forwarding
.endif

	RESTORE_REGS

.endmacro

汇编流程:
请添加图片描述
可看出关键代码是_lookUpImpOrForward做了些操作,查到了imp,由于是一个下划线_,那么我们直接在底层代码查找即可。

(三)有关下划线

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

(四)代码调试

博主是个大菜b,一大堆源码和根本看不懂的汇编真是把我整晕了😵‍💫😵‍💫😵‍💫,跟着别人的博客调试一遍吧,眼见为实。经典打上断点,使用Debug->Debug Workflow->Always shows Disassembly查看汇编。
代码就写成这样:
请添加图片描述
紧接着就会看到这样的,说明进入了汇编语言部分,我们使用control + Step into的方式一步步调试。
请添加图片描述

我们按下去,发现会进入objc_msgSend请添加图片描述
在控制台读取x1:
请添加图片描述
接下来正式进入objc_msgSend,br要跳转前,可以看到,参数x0就是第一个参数:类信息,x1就是第二个参数:方法信息。
请添加图片描述
接下来正式进入objc_msgSend流程:
请添加图片描述
一步步点,循环了几次,最后会进入_objc_msgSend_uncached
请添加图片描述
接下来,会进入lookUpImpOrForward
请添加图片描述

二、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 may have been dropped but is now locked again
    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_t *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;
            }
        }

        ...
        
    }
    ...
 done:
    ...
 done_unlock:
    ...
    return imp;
}

这里内容比较多,废话不多说,直接开始看。

(一)checkIsKnownClass

lookUpImpOrForward里面先调用了这个方法,先看一下调用这个 方法前给出的注释:

// 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.
//
// 我们不希望人们能够制作一个看起来像类但实际上不是一个类的二进制 blob 并进行CFI攻击。
// 为了使这些更难,我们希望确保这是一个内置于二进制文件中或通过objc_duplicateClass,
// objc_initializeClassPair或objc_allocateClassPair合法注册的类。
/***********************************************************************
* 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);
    }
}

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

(二)realizeAndInitializeIfNeeded_locked

在调用了checkIsKnownClass后,紧接着调用了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.
**********************************************************************/
/*
如果尚未实现,请实现给定的类,如果尚未初始化,则初始化它。
inst 是 cls 或子类的实例,如果没有已知,则为 nil。
cls 是要初始化和实现的类。
初始值设定项为 true 用于初始化类,false 表示跳过初始化。
 */
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

1. realizeClassMaybeSwiftAndLeaveLocked

static Class
realizeClassMaybeSwiftAndLeaveLocked(Class cls, mutex_t& lock)
{
    return realizeClassMaybeSwiftMaybeRelock(cls, lock, true);
}

2. realizeClassMaybeSwiftMaybeRelock

/***********************************************************************
* 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.
**********************************************************************/
/*
实现一个可能是 Swift 类的类。
返回类的实际类结构。
*/
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;
}

3. realizeClassWithoutSwift

/***********************************************************************
* 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
**********************************************************************/
/*
对类 cls 执行首次初始化,包括分配其读写数据。
不执行任何 Swift 端初始化。
返回类的实际类结构。
*/
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));

    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 数据已分配。
        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);

    ...

    return cls;
}

在这个方法里可看到主要是先对rwro的一些数据处理,之后处理了superclsmetacls

  1. supercls = cls->getSuperclass()递归取父类,再调用自身方法realizeClassWithoutSwift,即确定父类信息。
  2. metacls = cls->ISA()递归取元类,再调用自身方法realizeClassWithoutSwift,即确定元类信息。
  3. 之后调用cls->setSuperclass(supercls), cls->initClassIsa(metacls)将当前类的父类链、元类链确认绑定。

有关父类、元类这部分遗忘的知识,可以参考:[OC学习笔记]浅学元类
看了这部分代码,我们大概明白源代码的思路了,知道一个类,就层层嵌套,将父类/元类都初始化,方便之后找方法:

  • 实例方法:当前类没有去父类找,父类没有再去根类找……
  • 类方法:当前元类没有去父元类找,父元类没有再去根元类找……

(三)二分查找

继续往后面走,就会来到for (unsigned attempts = unreasonableClassCount();;),一个死循环,只能通过breakreturn来结束。

    // 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().
    /*
     用于在获取锁后立即再次查找类的缓存的代码,但在大多数情况下。
     有证据表明,这在大多数时候都是失误,因此是时间损失。
     在没有执行某种缓存查找的情况下调用此代码的唯一代码路径是class_getInstanceMethod()。
     */
     
	// 是因为多线程同步问题,还要再在cache里查找一次
    // 判断curClass是否被缓存, 防止因为上面不断调用ro, rw被对应方法被缓存, 如果当前类可以根据 sel找到imp, 即走 if 判断,
    // 不过通常是没有的
    for (unsigned attempts = unreasonableClassCount();;) {
        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_t *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

1. getMethodNoSuper_nolock

/***********************************************************************
 * 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?
    
    // 从类中获取方法列表methods
    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.
        /*
         getMethodNoSuper_nolock是search_method_list最热门的调用方,
         内联它将getMethodNoSuper_nolock转换为无帧函数,
         并从此代码路径中消除任何存储。
         */
        
        // 从方法中查找
        method_t *m = search_method_list_inline(*mlists, sel);
        if (m) return m;
    }

    return nil;
}

遍历时调用search_method_list_inline查找

2. 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;
    }

#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;
}

可以看出,他既然分成两种查找,一种在有序里查找,一种在无序里查找,肯定要对有序进行了优化,想必就是二分。我们迫切想看到它是怎么实现的,会发现它有两个实现:
请添加图片描述
点进第一个看看:

ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
    if (list->isSmallList()) {
        if (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS && objc::inSharedCache((uintptr_t)list)) {
            return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSEL(); });
        } else {
            return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSELRef(); });
        }
    } else {
        return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.big().name; });
    }
}

发现这个方法确有两个参数,最后都会调用它有三个参数的“兄弟”方法。那么我们直接去看三个参数的方法实现吧!

3. 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) {
            // `probe` is a match.
            // Rewind looking for the *first* occurrence of this value.
            // This is required for correct category overrides.
            // “probe”是匹配项。
            // 倒回去查找此值的 第一次 出现。
            // 这是正确的类别覆盖所必需的。
            // 做一步分类重名排查处理
            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;
}

下面看下流程图帮助理解:
请添加图片描述
接着我们看一下分类排查的部分:

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

(四)缓存找到的方法

对应的代码如下:

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

使用了goto语句,我们看一下done那里写了什么:

/* method lookup */
/*
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);
    }

看一下preoptFallbackClass()

Class cache_t::preoptFallbackClass() const
{
    return (Class)((uintptr_t)cls() + preopt_cache()->fallback_class_offset);
}

最后使用了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 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);
}

到了这里,我们发现即使方法是从父类的类对象/元类对象中找到的,这个方法缓存也是存在
自己的cache的。
可以浅看一下写日志操作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;
        }
    }
    ...
    return false;
}

所以,实际上是这样的:

  • 第一次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;
		}
	}

// Halt if there is a cycle in the superclass chain.
if (slowpath(--attempts == 0)) {
	_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
// 调用cache_getImp方法
imp = cache_getImp(curClass, sel);

我们搜索一下cache_getImp,发现它是汇编:

STATIC_ENTRY _cache_getImp

	GetClassFromIsa_p16 p0, 0
	CacheLookup GETIMP, _cache_getImp, LGetImpMissDynamic, LGetImpMissConstant

LGetImpMissDynamic:
	mov	p0, #0
	ret

LGetImpMissConstant:
	mov	p0, p2
	ret

	END_ENTRY _cache_getImp

可看出其重新调用了CacheLookup方法,我们回过头去看一下CacheLookup里:

.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
...
cbz p9, \MissLabelDynamic       //if (sel == 0) goto Miss;// 如果取出的sel位nil,则goto Miss
...

这里要留意:父类如果没找到会调用一个MissLabelDynamic 方法,而MissLabelDynamic是传入的方法(第二个参数),我们回头要看_cache_getImp调用时传入的方法:

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())。

总结

顺序:

  1. 父类快速查找→
  2. 父类慢速 →
  3. 根类快速 →
  4. 根类慢速→
  5. 直到 nil

但是实际上顺序是:

  1. 父类快速查找→
  2. 如果在父类的缓存中没有找到,那么就会循环执行父类的慢速查找→
  3. 在执行父类的慢速查找时,如果在父类的methodlist中也没有找到,那么就会找父类的父类(循环)→
  4. 当父类为nil时,那么imp就会赋值为forward_imp,跳出循环
    或者在父类中找到方法,跳出循环。

中间其实有个再循环操作
请添加图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值