iOS 底层原理之消息慢速查找

111 阅读4分钟

引言

上篇中在探索方法的本质是通过objc_msgSend(sel, id)进行消息发送,我们知道objc_msgSend(sel, id)是通过汇编实现的,在cache中找不到方式时走到了lookUpImpOrForward,然而这个方法是通过c++实现的,为什么objc_msgSend(sel, id)是用汇编实现的?这样做的好处在于,一是汇编语言更接近机器语言,并且使用了缓存快速优化查找时间,二是当我们参数不确定时(动态参数)c++不能更好的适配,而汇编更加的动态化。下面分析一下lookUpImpOrForward源码(捡主要代码分析)。

lookUpImpOrForward 源码分析

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)

{
    // 1.
    checkIsKnownClass(cls);
    
    // 2.
    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);

    curClass = cls;
    
    // 3.
    for (unsigned attempts = unreasonableClassCount();;) {

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

        }

    }

    // Halt if there is a cycle in the superclass chain.

    if (slowpath(--attempts == 0)) {

        _objc_fatal("Memory corruption in class list.");

    }
    // Superclass cache.

    imp = cache_getImp(curClass, sel);

    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;

    }

 done:
    log_and_fill_cache(cls, imp, sel, inst, curClass);

 done_unlock:
    return nil

  return imp;
}
1.checkIsKnownClass(cls)

checkIsKnownClass(cls) 是判断当前类是否已注册在缓存表中。主要代码:

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

}
2.realizeClassWithoutSwift(Class, Class)

realizeClassWithoutSwift(Class, Class) 实现类。主要有下面两部分

2.1 从磁盘中获取类的数据,并赋值给当前类

// 从磁盘中获取到类的数据,data中包含了methodList, property, protocol 等数据
auto ro = (const class_ro_t *)cls->data();

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

2.2 按照isa继承链初始化类

70a675b075024a9eae683ea97fd7b8ba~tplv-k3u1fbpfcp-zoom-in-crop-mark-3024-0-0-0.image.png

按照isa继承链 递归初始化类,主要代码实现如下:


// 获取类的父类 -> ... ->  NSObject -> nil 按照这个链条加载类
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
// 获取元类的父类 -> ... -> 根元类 -> NSOject -> nil 按照这个链条加载元类
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
// 给类赋值父类
cls->setSuperclass(supercls);
// 给元类赋值父元类
cls->initClassIsa(metacls);

3.二分查找法遍历methodList

3.1 getMethodNoSuper_nolock(curClass, sel)cls->data()->methods()获取方法列表,这里的methods是二维数组,search_method_list_inline(*mlists, sel)对二维数据进行排序,并使用二分法进行查找imp

static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel){
    auto const methods = cls->data()->methods();

   for (auto mlists = methods.beginLists(),
             end = methods.endLists();
             mlists != end;
             ++mlists) {
        method_t *m = 
        if (m) return m;
    }
    return nil;
}

3.2 findMethodInSortedMethodList(sel, method_list_t,getNameFunc)methodsList排序后,使用二分法查找imp

static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName){
    auto first = list->begin();
    auto base = first;
    decltype(first) probe;

    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;

    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);
        uintptr_t probeValue = (uintptr_t)getName(probe);

        if (keyValue == probeValue) {
            // This is required for correct category overrides.
// 当类的实例方法和分类的方法相同时,分类的方法会覆盖类的方法
            while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
                probe--;
            }
            return &*probe;
        }

        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    return nil;
}

例如,0-9,我们要查找的数是7,使用上方的算法走一遍流程:

keyValue = "7", base = 0, list = 0-9

第一轮:

count = 10,

probe = base + (count >> 1) = 0 + 1010 >> 1 = 0101 = 5,

probeValue = "5",

"7" > "5", base = probe + 1 = 5 + 1 = 6, count = 9

第二轮:

count >>= 1, 即 1001 >> 1 = 0100 = 4

probe = base + (count >> 1) = 6 + 0100 >> 1 = 6 + 0010 = 8

peobeValue = "8"

"8" > "7" 不成立

第三轮:

count >> 1 = 0100 > 1= 0010 = 2

probe = base + (count >> 1) = 6 + 0010 >> 1= 6 + 0001 = 6 + 1 = 7

probeValue = "7"

keyValue == probeValue = "7" 便找到了我们要的数据。

4. 父类查找方法

如果使用二分查找当前类没有找到相应的method

① 将当前类的父类赋值给当前类,如果当前类的父类是nil,则imp = forward_imp,进行方法转发

② 首先使用cache_getImp()使用汇编的方式快速查找方法,如果汇编中没有找到该方法,则依旧会来到消息的慢速查找流程中

小结:方法的查找流程为,当前类 -> 父类 -> ... -> NSObject -> nil,若中间找到方法,则写入缓存,若当查找到nil的时候也就是找遍了继承链也没找到方法时,则将imp = forward_imp进行方法转发。

// 将当前类的父类赋值给当前类
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
    // No implementation found, and method resolver didn't help.
    // Use forwarding.
    // 如果当前类的父类是nil,则imp = forward_imp,进行方法转发
    imp = forward_imp;
    break;
}
// 首先使用cache_getImp()使用汇编的方式快速查找方法,如果汇编中没有找到该方法,则依旧会来到消息的慢速查找流程中
imp = cache_getImp(curClass, sel);

5. done || done_unlock

done写入缓存中:

done:
    log_and_fill_cache(cls, imp, sel, inst, curClass);

done_unlock消息转发:

imp == forward_imp