OC - objc_msgSend()慢速查询过程

336 阅读6分钟

前言

通过 OC - Runimte & objc_msgSend()OC - Runimte & objc_msgSend()-下 这两文章我们对Runtime 有了初步了解。知道了 Runtime 编译时和运行时的区别,以及三种调用方式。还了解了objc_msgSend() 通过 selimp 的过程。接下来我们就通过这篇文章一起探索一下 objc_msgSend() 的慢速查询过程。

(补)objc_msgSend() 缓存找不到

OC - Runimte & objc_msgSend()-下 这篇文章最后提到如果缓存完全找不到时,就会走的 MissLabelDynamic(__objc_msgSend_uncached) 中。接下下来我们就全局搜索 __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

__objc_msgSend_uncached 中就会走到 MethodTableLookup 中开始查查找,然后走到 TailCallFunctionPointer 调用返回。

TailCallFunctionPointer 实现

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

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 // x16(class) 赋值给 x2
    mov	x3, #3  // 3 赋值给 x3
    bl	_lookUpImpOrForward

    // IMP in x0 -> 返回(x0 是第一个寄存器,同样也是返回值的 存储位置。)
    // 将 x0 赋值给 x17
    mov	x17, x0

RESTORE_REGS MSGSEND

.endmacro

MethodTableLookup 中,我们得知 IMPx0(x0 是第一个寄存器,同样也是返回值的 存储位置。)。由此我们可以推断出 _lookUpImpOrForward。然后我们全局搜索 _lookUpImpOrForward ,发现在汇编代码里面并没有 _lookUpImpOrForward 的定义和实现,接下来我们全局搜索 lookUpImpOrForward,并且能够搜索到结果。汇编 -> C++ 的过程只是一个 _ 的标识。

image.pnglookUpImpOrForward 中我们发现最后的返回值是 imp

疑问:为什么缓存要用汇编写而不是C++写呢?

  • 汇编整个流程更加接近机器语言,执行流程非常快。缓存查找流程中快速找到缓存。
  • 方法中的参数是不确定的,但是在C语言中参数必须明确,汇编可以更加的动态化。

lookUpImpOrForward

lookUpImpOrForward 汇编源码

lookUpImpOrForward 汇编源码我们应该怎么弄看呢?思路:查看它的返回值imp。看它是在那些地方进行了操作。接下来我们一起对源码进行分析

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    // 初始化 定义消息转发 forward_imp
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;

    runtimeLock.assertUnlocked();
    // 判断类是否初始化
    if (slowpath(!cls->isInitialized())) {
        behavior |= LOOKUP_NOCACHE;
    }

    runtimeLock.lock();
    // 检查类(class)是否被注册
    checkIsKnownClass(cls);
    // 初始化类和元类
    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
    // runtimeLock may have been dropped but is now locked again
    runtimeLock.assertLocked();
    curClass = cls;
    // 死循环,在遇到 break、goto出口时跳出循环。
    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.
            // 在curClass类中采用二分查找算法查找methodlist
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                imp = meth->imp(false);  // 获取对应的imp,并且跳动 done 流程。
                goto done;
            }
            // curClass = curClass->getSuperclass() 直到为nil走if里面的流程,不为nil走下面流程
            if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
                // No implementation found, and method resolver didn't help.
                // Use forwarding.
                // 在循环中没有找到对应的sel方法,就把定义的 forward_imp赋值给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. // 去父类的缓存中查找imp
        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;
        }
    }

    // No implementation found. Try method resolver once.

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

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

realizeAndInitializeIfNeeded_locked 实现

static Class
realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize)
{
    runtimeLock.assertLocked();
    // !cls->isRealized()小概率发生 cls->isRealized()大概率是YES
    // 判断类是否实现 目的是实现isa走位图中的isa走位链和父类链
    if (slowpath(!cls->isRealized())) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
        // runtimeLock may have been dropped but is now locked again
    }
    // 类是否初始化 没有先去初始化
    if (slowpath(initialize && !cls->isInitialized())) {
        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;
}

二分查找算法

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;
}
template<class getNameFunc>
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
    ASSERT(list);
    // 第一个method的位置
    auto first = list->begin();   
    auto base = first;
    decltype(first) probe;
    //  把key直接转换成uintptr_t 因为修复过后的method_list_t中的元素是排过序的
    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    /**
     *  比如:count = list->count = 8;
     *  count >> 1  =>  1000 -> 0100  => count = 4;
     *  probe = base + (count >> 1); => probe = 0 + 4;
     *  base = probe + 1;  => base = 5;
     *  count--;  => count = 8 - 1;
     *  继续循环
     *  count >>= 1 =>  0111 -> 0011 => count = 3
     *  count >> 1 => 0011 -> 0001 => count = 1
     *  probe = base + (count >> 1); => probe = 5 + 1;
     *  base = 5,list->count = 8,即它的取值是在 67。
     */
    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);
        // 获取中间的sel的值也是强转后的值
        uintptr_t probeValue = (uintptr_t)getName(probe);
        // 如果 目标key ==  中间位置的key 匹配成功
        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--;
        }
    }
    // 没找到返回 nil
    return nil;
}

通过对 findMethodInSortedMethodList 我们了解了 二分查找算法二分查找 又叫 折半查找,是一种简单又快速的查找算法;它对要查找的序列有两个要求,一是该序列必须是有序的(即该序列中的所有元素都是按照大小关系排好序的,升序和降序都可以),二是该序列必须是顺序存储的。

例如:在 0 ~ 100 中快速找到目标值 66 。

cache_getImp分析

    STATIC_ENTRY _cache_getImp
    // GetClassFromIsa_p16 宏定义和我们开始在本类中查询缓存方法一样
    GetClassFromIsa_p16 p0, 0
    CacheLookup GETIMP, _cache_getImp, LGetImpMissDynamic, LGetImpMissConstant

LGetImpMissDynamic:
    mov	p0, #0
    ret

GetClassFromIsa_p16 定义

.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */

#if SUPPORT_INDEXED_ISA
	// Indexed isa
	mov	p16, \src			// optimistically set dst = src
	tbz	p16, #ISA_INDEX_IS_NPI_BIT, 1f	// done if not non-pointer isa
	// isa in p16 is indexed
	adrp	x10, _objc_indexed_classes@PAGE
	add	x10, x10, _objc_indexed_classes@PAGEOFF
	ubfx	p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS  // extract index
	ldr	p16, [x10, p16, UXTP #PTRSHIFT]	// load class from array
1:

#elif __LP64__
.if \needs_auth == 0 // _cache_getImp takes an authed class already
	mov	p16, \src
.else
	// 64-bit packed isa
	ExtractISA p16, \src, \auth_address
.endif

_cache_getImp 调用 GetClassFromIsa_p16 时,存入了 p00,即 src:p0;needs_auth:0,所以在 GetClassFromIsa_p16 中直接走needs_auth==0p0=curClass。把p0寄存器的值赋值给p16寄存器,p16= curClass。这里就走到了我们前面说到的 objc_msgSend()缓存找不到 里面。

总结

慢查询流程图

未命名文件 (1).png