OC底层原理(九):objc_msgSend消息的慢速查找分析

467 阅读9分钟

一、消息的慢速查找_objc_msgSend_uncached

由于上一章里我们是第一次进入快速查找,没有找到方法后,它进入了_objc_msgSend_uncached慢速查找

cache内查找bucket_t的过程中,如果查找了所有的缓存也无法命中的时候,接下来就要进入消息的慢速查找流程了,也就是由汇编查找 -> C/C++代码查找。

慢速查找流程的图解

__objc_msgSend_uncached此方法是进入慢速查找流程的起因,此方法是作为参数传递进入CacheLookup函数的,如果CacheLookup查找不到imp的时候会执行MissLabelDynamic,也就是执行了__objc_msgSend_uncached

graph TB
subgraph C++语言
A[lookUpImpOrForward] --> B[getMethodNoSuper_nolock] -->  C[findMethodInSortedMethodList] --> D[log_and_fill_cache] --> E[insert]
end
subgraph 汇编语言
F[__objc_msgSend] --> G[CacheLookup] --> |无法命中缓存|H[__objc_msgSend_uncached] --> I[MethodTableLookup] -->J[TailCallFunctionPointer x17]
end

I -.-> |具体实现流程| A

慢速查找__objc_msgSend_uncached的流程:

  1. CacheLookup汇编中,当无法找到缓存的时候,会执行MissLabelDynamic,也就是__objc_msgSend_uncached

  2. 汇编中__objc_msgSend_uncached会执行MethodTableLookupTailCallFunctionPointer x17

  3. MethodTableLookup执行四个指令,将x16保存类地址移到x2,给x3赋值#3(即LOOKUP_INITIALIZE | LOOKUP_RESOLVER),跳转到c++源码中的lookUpImpOrForward方法,将x0对象移到x17

    • lookUpImpOrForward,找到关键代码,是一个死循环查找for (unsigned attempts = unreasonableClassCount();;)

    • ② 加线程锁、进入二分查找法

    • ③ 如果找到,log_and_fill_cache,将找到的imp插入缓存,下一次objc_msg_Send将进行快速查找

    • ④ 如果找不到,通过isa的查找链一直向上查找:共享缓存 -> methodList -> 父类 -> NSObject -> nil -> 跳出循环。最终都找不到的话,也就是superclass=nil的时候,将imp = forward_imp,进入消息转发流程(下一节内容)。

  4. TailCallFunctionPointer x17,拿到上一步的查找结果将跳转至寄存器x17

二、__objc_msgSend_uncached源代码分析

  • 汇编源代码:
STATIC_ENTRY __objc_msgSend_uncached
	UNWIND __objc_msgSend_uncached, FrameWithNoSaves
	
	//调用C++代码执行慢速查找流程
	MethodTableLookup
	//跳转到x17寄存器,等待汇编后续逻辑处理
	TailCallFunctionPointer x17

END_ENTRY __objc_msgSend_uncached

MethodTableLookup

保存信息方法,x0receiverx1_cmd,即sel;先将x16(LGPerson类)存在x2寄存器,将0x03存在x3寄存器,然后跳转至关键方法lookUpImpOrForward,这个方法返回值在汇编的角度来看是储存在x0寄存器的,所以当lookUpImpOrForward执行完成,将返回值x0存入x17.

  • 汇编源代码:
.macro MethodTableLookup	
	SAVE_REGS MSGSEND

	// lookUpImpOrForward(obj, sel, cls, behavior)
	// receiver and selector already in x0 and x1
	//lookUpImpOrForward方法需要4个参数:
	//obj      = x0
	//sel      = x1
	//cls      = x2
	//behavior = x3 = 3
	mov	x2, x16
	mov	x3, #3
	//bl:跳转指令,并将回家的路存在x30寄存器(lr)
	bl	_lookUpImpOrForward

	// 经过_lookUpImpOrForward拿到的返回值(IMP)将存在x0位置
	// 将x0赋值给x17
	mov	x17, x0

	RESTORE_REGS MSGSEND

.endmacro

TailCallFunctionPointer

//A12以上芯片(支持arm64e结构)
#if __has_feature(ptrauth_calls)

//通过慢速查找流程找到了IMP,那么这里就是将跳转至寄存器x17
//以提供后续汇编继续执行
.macro TailCallFunctionPointer
	// $0 表示的是参数x17
	braaz	$0
.endmacro
#else
.macro TailCallFunctionPointer
	// $0 表示的是参数x17
	br	$0
.endmacro
  • 结论:
  1. 快速查找流程无法命中的时候会进入慢速查找流程

  2. 慢速查找流程__objc_msgSend_uncached会执行2条指令,MethodTableLookup去查找并返回impTailCallFunctionPointer跳转至x17寄存器,也就是imp所在的寄存器

  3. 慢速查找流程将会进入C++中执行,然后通过x0寄存器返回,继续在汇编中向下执行,方法查找流程最终会跳转至x17寄存器(无论是慢速查找还是快速查找)。

三、lookUpImpOrForward(慢速查找流程核心)

lookUpimpOrForward流程的图解:

截屏2023-01-04 下午6.03.49.png

lookUpimpOrForward的源码分析

// 缓存 找不到 - lookUpImpOrForward - 慢速 - methodlist
// 汇编 缓存 参数未知
NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    //创建forward_imp,并给定默认值_objc_msgForward_impcache(具体在汇编objc-msg-arm64.s:跳转到__objc_msgForward)
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    //创建imp,用于接收通过sel查找的imp
    IMP imp = nil;
    //创建要查找的类,这个类通过isa的指向关系是会一直变化的,
    //直到最终指向NSObject的父类nil为止
    Class curClass;

    runtimeLock.assertUnlocked();

    if (slowpath(!cls->isInitialized())) {
        /**
        发送到类的第一条消息通常是 +new 或 +alloc 或 +self
        但是,此时该类尚未初始化,此时behavior = 3|8 = 11
        当向将上+new等这些方法inset进缓存的时候
        不满足behavior & LOOKUP_NOCACHE) == 0这个条件,8 & 11 = 8
        所以上述这些方法不会加载进缓存。
        
        如果类已经初始化了,就不会修改behavior的值了,behavior=3
        我们自定义的方法是可以正常加载进缓存的。
        */
        behavior |= LOOKUP_NOCACHE;
    }

    // runtimeLock 在 isRealized 和 isInitialized 检查期间被持有
    // 防止与并发实现竞争。
    runtimeLock.lock();

    //检查类是否被注册了
    //防止人为制造类,进行CFI攻击
    checkIsKnownClass(cls);

    /**初始化跟cls实例对象在isa指向图中的每一个类(class和metaClass)
    以便后续自己类里面找不到方法去父类里面找
    依次向上找
    所以在此处对所有相关的类进行了初始化
    */
    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
    // runtimeLock may have been dropped but is now locked again
    runtimeLock.assertLocked();
    //curClass为当前实例对象的类
    curClass = cls;

    /**
    * 循环查找类对象的methodList,当前类没有的话就找父类
    * 父类没有就找父类的父类,一直找到NSObject类
    * 如果NSObject都找不到的话最终curClass会指向nil
    * 将事先准备好的forward_imp赋值给imp
    * 然后结束慢速查找流程,接下来进入Runtime消息转发机制
    */
    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 {
            /**
            *在当前类的方法列表里面查找,这是重点
            *查找算法是二分法
            */
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                imp = meth->imp(false);
                goto done;
            }
            /**
            *如果当前类找不到,取将curClass指向superclass
            *查询父类的methodList,一直找到NSObject的父类nil为止
            * 将事先准备好的forward_imp赋值给imp
            * 然后结束慢速查找流程,接下来进入Runtime消息转发机制
            * 结束循环遍历
            */
            if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
                imp = forward_imp;
                break;
            }
        }

        // 类列表中的内存损坏
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }

        // 在父类的缓存中查找,这里再次进入汇编查找流程
        imp = cache_getImp(curClass, sel);
        //如果没有找到,将默认的forward_imp赋值给imp
        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)) {
            //将找到的method插入到缓存中,以便下次查找使用快速缓存查找
            goto done;
        }
    }

    // No implementation found. Try method resolver once.

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

//找到了sel对应的imp,将method方法加载进缓存
 done:
    if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
        while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
            cls = cls->cache.preoptFallbackClass();
        }
#endif
        log_and_fill_cache(cls, imp, sel, inst, curClass);
    }
 done_unlock:
    runtimeLock.unlock();
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;
}

realizeClassWithoutSwift

只列出了2行关键源码,用来表示递归初始化类元类

static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
}

getMethodNoSuper_nolock(二分查找流程)

static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();

    ASSERT(cls->isRealized());
    
    //拿到cls的data()里面存的methods()
    auto const methods = cls->data()->methods();
    for (auto mlists = methods.beginLists(),
              end = methods.endLists();
         mlists != end;
         ++mlists)
    {
        //进入search_method_list_inline修复为有序list
        //从方法列表中找当前sel
        //类中自定义的方法第一次循环的时候走这里,存在methods.beginLists()

        method_t *m = search_method_list_inline(*mlists, sel);
        if (m) return m;
    }

    return nil;
}

search_method_list_inline

修复函数,来函使methodList变得有序

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

    return nil;
}

findMethodInSortedMethodList(已经排好序的methodlist)

isSmallList代表的是m1的电脑。

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

findMethodInSortedMethodList(二分查找真正的实现方法)

template<class getNameFunc>
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
    ASSERT(list);
    //开始位置:0
    auto first = list->begin();
    //base开始也为0
    auto base = first;
    //probe也为0
    decltype(first) probe;
    //要查找imp对应的sel
    uintptr_t keyValue = (uintptr_t)key;
    //list的个数
    uint32_t count;
    
    /**
    * 举例:假设要查找的sel在第7位
    * 首先count = list.count,这里假定count=8
    * 进入循环,probe = base + ( 8 >> 1 ) = 0 + 4 = 4
    * 那么第一次查找的范围就是4-8,匹配元素位置是4,判定结果keyValue(7)> prebeValue(4),未匹配
    * 满足keyValue > probeValue,base = probe + 1 = 4 + 1 = 5,count-- = 7
    * 第二次进入循环,此时count = 7 >> 1 = 3, probe = 5 + (3 >> 1) = 6
    * 第二次查找的范围是6-7,匹配元素位置是6,判定结果keyValue(7)> prebeValue(6),未匹配
    * 满足keyValue > probeValue,base = probe + 1 = 6 + 1 = 7,count-- = 2
    * 第三次进入循环,此时count = 2 >> 1 = 1, probe = 7 + (1 >> 1) = 7
    * 第三次查找的元素是7,匹配,返回imp
    */
    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);
        
        uintptr_t probeValue = (uintptr_t)getName(probe);
        
        if (keyValue == probeValue) {
            //向前寻找第一个出现的imp,为了避免分类方法于主类方法相同时问题
            //这也就是为什么分类方法会被加载的原因
            while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
                probe--;
            }
            return &*probe;
        }
        
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    
    return nil;
}

log_and_fill_cache(缓存填充)

如果记录器允许,调用cache的insert方法,将其插入缓存,下一次的查找就会进行快速缓存查找了。

static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
    if (slowpath(objcMsgLogEnabled && implementer)) {
        bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                      cls->nameForLogging(),
                                      implementer->nameForLogging(), 
                                      sel);
        if (!cacheIt) return;
    }
#endif
    cls->cache.insert(sel, imp, receiver);
}
  • 结论:
  1. lookUpImpOrForward,此过程为慢速查找流程,通过死循环的方式不断遍历来查找imp,对于全局性来讲,首先去找共享缓存,然后查自己的methodList;如果自己没有,找父类,父类没有,找NSObjectNSObject没有,会指向nil`,最终跳出来。

  2. 流程简化:共享缓存 -> methodList -> 父类 -> NSObject -> nil-> 跳出循环

四、二分法查找法

使用C++代码还原二分查找法,假设二分法要找的内容是第3

  • 案例代码:
#include <iostream>

using namespace std;

int main(int argc, const char  argv[]) {

    auto first = 0;
    auto base = first;
    decltype(first) probe = 0;
    uintptr_t keyValue = 3;
    uint32_t count;

     
    for (count = 8; count != 0; count >>= 1) {
    
        cout << "前=count: "<< count << "---probe: " << probe << endl;
        //1. base = 0, count = 8, probe = 0 + (8 >> 1) = 4
        //2. base = 0, count = 8 >> 1 = 4, probe = 0 + (4 >> 1) = 2
        //3. base = 3, count = 3 >> 1 = 1, probe = 3 + (1 >> 1) = 3;
        probe = base + (count >> 1);
        cout << "后=count: "<< count << "---probe: " << probe << endl;

        //1: 3 != 4,没找到
        //2: 3 != 2,没找到
        //3: 3 == 3,找到了
        if (keyValue == probe) {

            cout << "找到了=keyValue: " << keyValue << "---probe: " << probe << endl;
            return 0;
            
        }
        else{

            cout << "没找到=keyValue: " << keyValue << "---probe: " << probe << endl;

        }

       //1: 3 < 4, base = 0,         count = 8;
       //2: 3 > 2, base = 2 + 1 = 3, count = 4 - 1 = 3;
        if (keyValue > probe) {
            base = probe + 1;
            count--;
        }
    }

    return 0;

}
  • 控制台打印结果:
前=count: 8---probe: 0

后=count: 8---probe: 4

没找到=keyValue: 3---probe: 4

前=count: 4---probe: 4

后=count: 4---probe: 2

没找到=keyValue: 3---probe: 2

前=count: 1---probe: 2

后=count: 1---probe: 3

找到了=keyValue: 3---probe: 3

Program ended with exit code: 0
  • 结论:
  1. 假设要查找的sel对应的在第3

  2. 首先count = list.count,这里假定count=8

  3. 第一次进入循环,probe = base + ( 8 >> 1 ) = 0 + 4 = 4

    • 那么第一次查找的范围就是4-8,匹配元素位置是4,判定结果keyValue(3) < prebeValue(4),未匹配
    • 不满足keyValue > probeValuebase = 0,count = 8
  4. 第二次进入循环,此时count = 8 >> 1 = 4, probe = 0 + (4 >> 1) = 2

    • 第二次查找的范围是1-4,匹配元素位置是4,判定结果keyValue(3) > prebeValue(4),未匹配
    • 满足keyValue > probeValuebase = probe + 1 = 2 + 1 = 3count-- = 3
  5. 第三次进入循环,此时count = 3 >> 1 = 1, probe = 3 + (1 >> 1) = 3

    • 第三次查找的元素是3,匹配,返回imp