前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >OC底层探索11-objc_msgSend慢速查找流程OC底层探索11-objc_msgSend慢速查找流程

OC底层探索11-objc_msgSend慢速查找流程OC底层探索11-objc_msgSend慢速查找流程

作者头像
用户8893176
发布2021-08-09 11:25:11
2850
发布2021-08-09 11:25:11
举报
文章被收录于专栏:小黑娃Henry

在上一篇文章中介绍了在汇编部分的缓存快速查找流程。由于首次调用或者缓存扩容等问题导致的缓存查找失败,就需要进入慢速查找流程.

objc_msgSend慢速查找

慢速查找入口-汇编部分
  • 在快速查找流程无法找到对应缓存的时候,会跳到CheckMiss\JumpMiss这个macro中并且走到__objc_msgSend_uncached这个函数中。
代码语言:javascript
复制
    STATIC_ENTRY __objc_msgSend_uncached
    UNWIND __objc_msgSend_uncached, FrameWithNoSaves

    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band p16 is the class to search
    
    MethodTableLookup   //跳转
    TailCallFunctionPointer x17 //寄存器调用

    END_ENTRY __objc_msgSend_uncached
  • 继续探索MethodTableLookup的实现,发现实现很长,但是都是一些寄存器的操作等,只看我们关心的b、bl
代码语言:javascript
复制
.macro MethodTableLookup
...

// 省略影响跟流程的代码
bl  _lookUpImpOrForward

...
  • 继续搜索_lookUpImpOrForward发现找不到对用的实现了。苹果编码有一个习惯通过前缀增加、减少_,来实现多种语言的切换。

通过调用lookUpImpOrForward,就来到了c++部分。

通过调试来跟踪流程

前文中的流程跳转有一部分猜测的成分,现在通过调试来验证一下之前的猜测.

  1. 打开断点,找到目标调用
  1. 打开堆栈信息选项
  1. 按住control 点击 Step + info
  1. 点击下一步来到__objc_msgSend_uncached
  1. 最终调用lookUpImpOrForward,而且给出了c++函数的位置

通过编译调试进一步验证了之前的猜测。

慢速查找c++流程
代码语言:javascript
复制
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    //参数准备
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;
    
    //再进行一次缓存的查找
    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);
    
    //判断当前类是否加载到内存中(isa和rw信息是否存在)。
    if (slowpath(!cls->isRealized())) {
        //如果不存在进行类的加载,并且会递归加载所有父类、元类。为了确定类的父类、isa链
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
    }
    
    //判断类是否初始化,同样是递归判断所有
    if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
        //对类进行初始化,并且会递归加载所有父类、元类。为了确定类的父类、isa链
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
    }
    
    runtimeLock.assertLocked();    //解锁
    
    //循环查找,无跳出条件
    for (unsigned attempts = unreasonableClassCount();;) {
        //使用二分法在当前类的方法列表methodList中查找该方法
        Method meth = getMethodNoSuper_nolock(curClass, sel);
        if (meth) {
        //本质就是查找方法的imp实现
            imp = meth->imp;
            goto done;
        }

        //1. 将当前类切换为父类
        //2. 如果当前类的父类是nil,也就是在NSObject中都没有找到则直接跳出循环
        if (slowpath((curClass = curClass->superclass) == nil)) {
            imp = forward_imp;
            break;
        }

        // 如果父类链中存在循环,则停止
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }

        //对父类进行方法查找
        //1.汇编缓存快速查找
        imp = cache_getImp(curClass, sel);
        //如果父类没找到,首次会进入动态方法解析
        if (slowpath(imp == forward_imp)) {
            break;
        }
        //在父类中找到了该方法
        if (fastpath(imp)) {
            goto done;
        }
    }
    
    //每个类首次都会进行一次方法动态解析
    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;
    }
    //返回查询结果imp
    return imp;

这就是整个消息imp查询闭环流程(快、慢)。

慢速查找流程图

objc_msgSend慢速流程.png

cache_getImp没有发生递归
代码语言:javascript
复制
STATIC_ENTRY _cache_getImp

GetClassFromIsa_p16 p0
CacheLookup GETIMP, _cache_getImp

.macro CheckMiss
    // miss if bucket->sel == 0
.if $0 == GETIMP
    cbz p9, LGetImpMiss
.elseif $0 == NORMAL
    cbz p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
    cbz p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
  1. 可以看到CacheLookup的第一个参数是GETIMP
  2. GETIMP下并没有调用__objc_msgSend_uncached,所以并不会产生递归,只会查找当前类的缓存
方法没有找到,而且没有实现方法解析

extern void _objc_msgForward_impcache(void);看到extern就知道它又是汇编代码编写的了

代码语言:javascript
复制
STATIC_ENTRY __objc_msgForward_impcache
//跳转到 __objc_msgForward
b   __objc_msgForward
END_ENTRY __objc_msgForward_impcache


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++编写,所以取一个_继续搜索
  • 相信这段话已经很熟悉了吧?平时开发应该见过无数次了。
MethodList(有序数组)二分查找

以上已经解释了慢速查找的整个流程,现在对MethodList二分查找的实现做一个解释。(核心方法findMethodInSortedMethodList)

代码语言:javascript
复制
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
    ASSERT(list);
    //当前list是有序的,在类创建的时候就已经进行了加载
    const method_t * const first = &list->first;
    const method_t *base = first;
    const method_t *probe;
    uintptr_t keyValue = (uintptr_t)key; //key 等于 say666
    uint32_t count;
    //base相当于low,count是max,probe是middle,这就是二分
    for (count = list->count; count != 0; count >>= 1) {
        //从首地址+下标 --> 移动到中间位置(count >> 1 右移1位即 count/2 = 4)
        probe = base + (count >> 1); 
        
        uintptr_t probeValue = (uintptr_t)probe->name;
        
        //如果查找的key的keyvalue等于中间位置(probe)的probeValue,则直接返回中间位置
        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;
}

示例图:

1. 向左查询

8中找1

2. 向右查询

8中找7

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/11/26 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • objc_msgSend慢速查找
    • 慢速查找入口-汇编部分
      • 通过调试来跟踪流程
        • 慢速查找c++流程
          • 慢速查找流程图
            • cache_getImp没有发生递归
              • 方法没有找到,而且没有实现方法解析
                • MethodList(有序数组)二分查找
                  • 1. 向左查询
                    • 2. 向右查询
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档