美文网首页底层原理
objc_msgSend 消息转发流程探究二

objc_msgSend 消息转发流程探究二

作者: 晨曦的简书 | 来源:发表于2021-07-04 18:57 被阅读0次

imp 查找不到的情况

我们在 objc_msgSend 消息转发流程探究一 中讲过,当我们调用底层 objc_msgSend 方法的时候,会在类的 cache 中查找 ssl对应的 imp 指针。当找到的时候就会走的 cacheHit 方法,缓存命中。这是快速查找流程,但是如果找不到的话在 objc_msgSend 消息转发流程探究一 中也讲了,会调用 __objc_msgSend_uncached 方法。这里就来看一下该方法往下的调用步骤。也就是消息的慢速查找流程。

__objc_msgSend_uncached 方法调用流程

  1. __objc_msgSend_uncached


    全局搜索找到 __objc_msgSend_uncached 方法的入口,这里看的是 arm64 架构下的源码。
  2. MethodTableLookup

.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 --
    // 这里意味着 IMP 存储在x0里面,如果在x0里面就必然要返回,因为x0是寄存器的第一个位置,也是返回值的存储位置,所以 IMP 在 bl _lookUpImpOrForward 这个方法的返回值里面
    mov x17, x0

    RESTORE_REGS MSGSEND

.endmacro

这里意味着 IMP 存储在 x0 里面,如果在 x0 里面就必然要返回,因为 x0 是寄存器的第一个位置,也是返回值的存储位置,所以 IMPbl _lookUpImpOrForward 这个方法的返回值里面。

  1. _lookUpImpOrForward

    同样全局搜索找到 lookUpImpOrForward 方法。

这里大家可以注意到一个点,为什么缓存的查找方法的流程要用汇编来写呢?这里总结了下几个原因。

  1. 汇编更接近底层,效率更高,能更快的查找到缓存
  2. 方法的参数不固定,c 或者 c++方法是不能满足的,而汇编方法能更加动态
  1. checkIsKnownClass(Class cls)
static void
checkIsKnownClass(Class cls)
{
    if (slowpath(!isKnownClass(cls))) {
        _objc_fatal("Attempt to use unknown class %p.", cls);
    }
}

在此方法中进入到 isKnownClass(Class cls) 方法。

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

这一步主要作用是判断当前 Class 是否注册到了缓存表当中。

  1. realizeAndInitializeIfNeeded_locked

realizeAndInitializeIfNeeded_locked -> realizeClassMaybeSwiftAndLeaveLocked -> realizeClassMaybeSwiftMaybeRelock -> realizeClassWithoutSwift

static Class realizeClassWithoutSwift(Class cls, Class previously)
{
// 1. 这里对 ro 及 rw 进行准备操作,对当前类进行初始化
    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 = 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);
    }

// 2. 对父类及元类分别执行 realizeClassWithoutSwift 方法, 该方法属于递归方法,父类及元类在执行 realizeClassWithoutSwift 方法时同时会对各自的父类及元类执行 realizeClassWithoutSwift 方法
    supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);


// 3. cls 的父类是设置为supercls, cls 类的 isa 设置指向为元类
    cls->setSuperclass(supercls);
    cls->initClassIsa(metacls);
}

通过该流程一步一步跟到 realizeClassWithoutSwift 方法,该方法主要做了三个作用。

  1. 对 ro 及 rw 进行准备操作,对当前类进行初始化(ro中有 MethodList 及 ProtocolList)
  2. 对父类及元类分别执行 realizeClassWithoutSwift 方法, 该方法属于递归方法,父类及元类在执行 realizeClassWithoutSwift 方法时同时会对各自的父类及元类执行 realizeClassWithoutSwift 方法
  3. cls 的父类是设置为supercls, cls 类的 isa 设置指向为元类

这里让我们联想到了著名的 isa 流程图。

isa流程图.png
  1. for (unsigned attempts = unreasonableClassCount();;)
   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)) {
                // No implementation found, and method resolver didn't help.
                // Use forwarding.
                imp = forward_imp;
                break;
            }
        }
}

这里会进到一个 for 循环,这里可以看到 for (unsigned attempts = unreasonableClassCount();;) 为死循环。执行的时候会先再次判断一次有没有缓存 CONFIG_USE_PREOPT_CACHES

  1. getMethodNoSuper_nolock(Class cls, SEL sel)
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)
    {
        // <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.
        method_t *m = search_method_list_inline(*mlists, sel);
        if (m) return m;
    }

    return nil;
}

这里会遍历查看类的 methods 方法列表。

  1. search_method_list_inline(const method_list_t *mlist, SEL sel)
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;
}
  1. findMethodInSortedMethodList(SEL key, const method_list_t *list)
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; });
    }
}

这里会判断是 smallList 结构还是 bigList 结构,因为 m1 的电脑会不一样。

  1. findMethodInSortedMethodList
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;
    // 1000 - 0100
    // 8 - 1 = 7 >> 0111 -> 0011 3 >> 1 == 1
    // 0 + 4 = 4
    // 5 - 8
    // 6-7
    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);
        
        uintptr_t probeValue = (uintptr_t)getName(probe);
        
        if (keyValue == probeValue) {
            // `probe` is a match.
            // Rewind looking for the *first* occurrence of this value.
            // 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;
}

这一步主要是二分遍历查找 method_list_t 中对应的 imp

  1. log_and_fill_cache
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);
}

这里查找到 imp 后会把对应的 selimp 添加到缓存中。

  1. cache_getImp(curClass, sel)
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;
        }

这里在当前类中没查到到对应的方法后,会到父类中取查找,父类查找的时候也是先走快速查找流程,快速查找流程找不到后会走慢速查找流程。如果慢速也没找到会查找父类的父类,就是一个递归查找的流程。

  1. forward_imp
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
                // No implementation found, and method resolver didn't help.
                // Use forwarding.
                imp = forward_imp;
                break;
            }

如果一直找到根类都没找到,就会把 imp 赋值为 forward_imp

相关文章

网友评论

    本文标题:objc_msgSend 消息转发流程探究二

    本文链接:https://www.haomeiwen.com/subject/ygscultx.html