九、消息流程—慢速查找

iOS 底层探索 文章汇总

主要内容:
一、分析慢速查找调用的方法
二、慢速查找源码及流程
三、慢速查找中部分具体实现

分析慢速查找调用的方法

上一章objc_msgSend 流程—快速查找文中分析,如果快速查找,没有找到时,会中转到CheckMiss/JumpMiss中标记为NORMAL,并跳转__objc_msgSend_uncached,本章继续分析

  • __objc_msgSend_uncached 汇编
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves

MethodTableLookup
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached

核心是 MethodTableLookup 即查询方法列表

  • MethodTableLookup 汇编实现
.macro MethodTableLookup
    
    // push frame
    SignLR
    stp fp, lr, [sp, #-16]!
    mov fp, sp
    ......省略......
    // 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
    mov x17, x0
    
    ......省略......

.endmacro

核心是 _lookUpImpOrForward

验证:通过汇编调试验证

    1. main 函数中 对某一对象方法调用加断点,运行到断点处,开启汇编调试Debug -> Debug worlflow -> Always show Disassembly,执行进入汇编
      1.png
  • 2.在 objc_msgSend行加断点,执行到断点处,按住control + stepinto, 进入objc_msgSend 汇编

    2.png

  • 3.在 _objc_msgSend_uncached行加断点,执行到断点处,按住control + stepinto, 进入_objc_msgSend_uncached 汇编,会发现最后执行到 lookUpImpOrForward

    3.png


c/c++ 调用汇编方法,查找该汇编方法时,要在方法前加 一个下划线
汇编方法 调用c/c++ 方法,查找该方法时,要在方法前 减掉一个下划线

二、慢速查找源码及流程

  • 全局搜索lookUpImpOrForward,在objc-runtime-new.mm文件中,源码如下:
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    // 定义的消息转发
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;

    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
    // 找缓存 - 快速查找,如果找到则直接返回 imp
    // 目的:防止多线程,调用函数时,刚好缓存进来
    if (fastpath(behavior & LOOKUP_CACHE)) {
        imp = cache_getImp(cls, sel);
        if (imp) goto done_nolock;
    }

   
    // 加锁,保证读取的线程安全
    runtimeLock.lock();

    // TODO: this check is quite costly during process startup.
    // 判断是否是一个已知的类,判断当前类是否已经被认可,即已经加载的类
    checkIsKnownClass(cls);
    //判断类是否实现,如果没有,需要先实现,此时的目的是为了确定父类链,方便后续的循环
    if (slowpath(!cls->isRealized())) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
        // runtimeLock may have been dropped but is now locked again
    }
    // 判断类是否初始化,如果没有,需要先初始化
    if (slowpath((behavior & LOOKUP_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
    }

    runtimeLock.assertLocked();
    curClass = cls;

    // 查找类的缓存
    // unreasonableClassCount - 表示类的迭代的上限
    // 死循环   (不存在递归)
    for (unsigned attempts = unreasonableClassCount();;) {
        // curClass method list.
        // 当前类方法列表(采用二分法查找),找到则返回,将方法缓存到cache中
        Method meth = getMethodNoSuper_nolock(curClass, sel);
        if (meth) {
            imp = meth->imp;
            goto done;
        }
        //curClas = 当前类的父类,并判断父类是否为nil
        if (slowpath((curClass = curClass->superclass) == 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.
            // 如果在父类中找到imp 的值是forward_imp,则停止查找,跳出循环
            break;
        }
        if (fastpath(imp)) {
            // Found the method in a superclass. Cache it in this class.
            // 如果在父类中,找到了方法,将其存储到cache中
            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:
    // 存储到缓存
    log_and_fill_cache(cls, imp, sel, inst, curClass);
    // 解锁
    runtimeLock.unlock();
 done_nolock:
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;
}

流程图

慢速查找流程.png

总结主要步骤

  1. cache 缓存中进行查找(快速查找),找到就返回,没有找到向下执行
  2. 判断cls,是否是已知类 否 -> 则报错类是否实现 否->实现并确定父类链,是否初始化 否 ->初始化
  3. for循环(是死循环),按照类继承链 或 元类继承链的顺序查找
  • 先在cls的 方法列表中使用二分法查找,找到: 则进入cache写入流程返回imp。未找到: cls = cls->superclass
  • 现在 cls 是父类,先判空,在cls 的缓存中查找,找到: 则进入cache写入流程返回imp,未找到:回到第三步 for 循环

4.判断是否执行过动态方法解析 否 -> 执行动态方法解析(只执行一次)

三、慢速查找中部分具体实现

下面分析 二分查找原理父类缓存查找步骤 及方法未实现时报错源码

  1. getMethodNoSuper_nolock 使用 二分法查找

方法查找流程:
getMethodNoSuper_nolock -> search_method_list_inline -> findMethodInSortedMethodList

findMethodInSortedMethodList 源码

ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
    ASSERT(list);

    const method_t * const first = &list->first;
    const method_t *base = first;
    const method_t *probe;
    // 当前查找的方法
    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    // base是第一个,count是最后一个,probe是中间。 二分查找
    // count>>=1   -> count = count >> 1
    for (count = list->count; count != 0; count >>= 1) {
        // 从首地址+下标 -> 移动到中间位置  count>>1 == count/2
        probe = base + (count >> 1);
        
        uintptr_t probeValue = (uintptr_t)probe->name;
        // 如果找到,返回中间位置
        if (keyValue == probeValue) {

            // while 平移 -- 排除分类重名方法
            while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
                // 分类方法在前面,优先调用分类的方法
                probe--;
            }
            return (method_t *)probe;
        }
        // 如果 keyValue 大于 probeValue, 就往probe即中间位置的右边查找
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    
    return nil;
}

执行流程

执行流程.png
  1. 汇编_cache_getImp 父类缓存查找,核心代码CacheLookup,CacheLookup在上一章objc_msgSend 流程—快速查找 有介绍
STATIC_ENTRY _cache_getImp
GetClassFromIsa_p16 p0
// 缓存查找
CacheLookup GETIMP, _cache_getImp

LGetImpMiss:
    mov p0, #0
    ret

END_ENTRY _cache_getImp

父类缓存查找流程:

image.png
  1. 方法未实现时报错
    如果在快速查找、慢速查找、方法解析流程中,都没有找到实现,则使用消息转发,其流程如下:
    __objc_msgForward_impcache -> __objc_msgForward -> __objc_msgForward -> __objc_forward_handler(c/c++实现_objc_forward_handler) -> objc_defaultForwardHandler

消息转发源码:

  • __objc_msgForward_impcache 汇编源码,跳转到 __objc_msgForward
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b   __objc_msgForward

END_ENTRY __objc_msgForward_impcache
  • __objc_msgForward汇编源码,核心是__objc_forward_handler
ENTRY __objc_msgForward

adrp    x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
    
END_ENTRY __objc_msgForward
  • __objc_forward_handler 对应的c++代码_objc_forward_handler,本质是objc_defaultForwardHandler方法
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
  • objc_defaultForwardHandler 源码,函数未实现时报的错
// Default forward handler halts the process.
__attribute__((noreturn, cold)) void
objc_defaultForwardHandler(id self, SEL sel)
{
    _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
                "(no message forward handler is installed)", 
                class_isMetaClass(object_getClass(self)) ? '+' : '-', 
                object_getClassName(self), sel_getName(sel), self);
}

你可能感兴趣的:(九、消息流程—慢速查找)