OC底层原理十三: objc_msgSend(方法慢速查找)

OC底层原理 学习大纲

梳理下调用方法的流程,避免大家迷路。

  • 我们对象(实例对象或类)调用方法,都是执行objc_msgSend:
  • step1: 进入汇编语言,在cache快速查找,找到了返回imp,没找到走step2
  • step2: 进入c/c++底层,在methodList中查找,(会将方法写入缓存,保障后续调用时,能直接在第一步就获取到imp),找到了的返回imp,没找到走step3
  • step3: 走最后处理机制(三重防护,这个后面详细介绍),没找到走step4
  • step4: 执行默认的imp,报错提示, crash

上一节已了解方法在cache中的高速查找流程,当cache中找不到imp时,我们进入step2

方法慢速查找流程

请跟着我,一步步细致理解。我相信你会收货满满的干货

  1. 了解高速cache转低速c/c++底层过程
  2. 初始值forward_imp
  3. 慢速前的cache读取
  4. 检查类是否合法
  5. 类的初始化(双向链表)
  6. 当前类和父类循环查找imp
  7. 最后的防线
  8. 查找结果

1. 了解高速cache转低速c/c++底层过程

  • 我们选择arm64真机环境,在objc4源码中进行探索。

  • 上一节我们在缓存找不到imp后,就来到了__objc_msgSend_uncached->MethodTableLookup方法列表查找 -> _lookUpImpOrForward

  • objc4源码中搜索_lookUpImpOrForward,发现没有具体实现。 取消_,搜索lookUpImpOrForward

总结.png
  • 发现lookUpImpOrForward不再是汇编语言,而是oc底层语言。这也是为什么说从这开始,就是从快速搜索(汇编)回到慢速搜索(c/c++)

代码检验流程

main.m中加入测试代码。 在[p sayHello];加入断点

@interface HTPerson : NSObject
- (void)sayHello;
@end

@implementation HTPerson
- (void)sayHello{
   NSLog(@"%s",__func__);
}
@end

int main(int argc, const char * argv[]) {
   @autoreleasepool {
       HTPerson *p  = [HTPerson alloc];
       [p sayHello];
   }
   return 0;
}
OC底层原理十三: objc_msgSend(方法慢速查找)_第1张图片
代码检验.jpg
  • lookUpImpOrForward的代码在OC底层原理十: 类的深入理解中有讲到,这里我们做更细致的分析。

2. 初始值forward_imp

lookUpImpOrForward函数第一步,设置初始变量(forward_imp、imp、curClass)

    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;

我们先弄清楚forward_imp是什么?

  • 全局搜索_objc_msgForward_impcache
OC底层原理十三: objc_msgSend(方法慢速查找)_第2张图片
image.png

注:
1、C/C++调用汇编,去汇编中查找时: 在方法前一个下划线
2、汇编调用C/C++方法,去C/C++中查找时: 在方法一个下划线

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

结论: forward_imp 的值就是未找到imp的函数实现

3. 慢速前的cache读取

在进入lookUpImpOrForward函数前,我们在汇编MethodTableLookup宏中了解behavior的值为3

OC底层原理十三: objc_msgSend(方法慢速查找)_第3张图片
image.png

lookUpImpOrForward函数继续往下看:

    if (fastpath(behavior & LOOKUP_CACHE)) {  
        imp = cache_getImp(cls, sel);
        if (imp) goto done_nolock;
    }

因为LOOKUP_CACHE = 4behavior = 3,二进制得出 behavior & LOOKUP_CACHE0

  • if判断条件不成立

初次进入lookUpImpOrForward是从缓存进入的,所以没必要再在缓存中进行查询,但是后续再次进入时,控制入参behavior的值,会触发cache_getImp缓存查询

我们看看cache_getImp,发现又回到了高速汇编层,全局搜索_cache_getImp:

OC底层原理十三: objc_msgSend(方法慢速查找)_第4张图片
image.png

发现调用了CacheLookup,继续在clscache高速查找当前sel对应的imp

CacheLookup高速查找流程在上一节已详细介绍

主要目的:

  • 可能多个线程都在执行objc_msgSend任务,在你第一次高速查找未找到时,可能其他线程已将cls的sel和imp写入了cls的缓存

  • 高速查找(汇编)低速查找(c/c++)性能太多了,我们进入慢速查找前,我们再走一次高速缓存查找。 (缓存读取不到再过来lookUpImpOrForward的,默认走了一遍高速缓存了,就没再进入了)

  • 如果获取到imp,就跳转到done_nolock(下面一起分析), 如果没有,进入慢速查找流程。

4. 检查类是否合法

 checkIsKnownClass(cls);

确保cls已知类列表中,避免传入一个非类二进制文件,进行CFI攻击

5. 类的初始化(双向链表)

确保cls已存在,将cls赋值给curClass

  • 如果不存在,就现在创建类( 因为swiftOC类的结构不一样,所以需要单独判断是否存在。)
   // 如果类没有实现或内部信息不完整
    if (slowpath(!cls->isRealized())) {
        // 实现类(swift和oc)  递归实现完整的继承链和isa指向链
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
    }

    // 如果类没有初始化
    if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
        // 完成类的初始化
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
    }

    // 此时运行时锁一定是锁定状态
    runtimeLock.assertLocked();
    
    // 拿到了cls类 (oc类或swift类)
    curClass = cls;
5.1 类的实现(完善继承链和isa链,双向绑定)
OC底层原理十三: objc_msgSend(方法慢速查找)_第5张图片
image.png

此处目的是cls的所有内容赋值,完成继承链isa指向链的完整绑定。便于后续沿着继承链搜索sel对应的imp

5.2 类的初始化

检查本类继承链上的类是否都初始化,确保后续可以对类进行操作。

OC底层原理十三: objc_msgSend(方法慢速查找)_第6张图片
image.png

6.当前类和父类循环查找imp

   // unreasonableClassCountt为 353056。一个足够大的值,只要比继承链类的个数大,就循环有效)
    for (unsigned attempts = unreasonableClassCount();;) {
        // 在curClass的函数列表中搜索sel方法
        Method meth = getMethodNoSuper_nolock(curClass, sel);
        // 如果存在,将imp赋值为sel对应的imp。跳到下面done继续操作
        if (meth) {
            imp = meth->imp;
            goto done;
        }
        
        // 如果没有找到, curClass赋值为curClass的父类,判断父类是否存在
        if (slowpath((curClass = curClass->superclass) == nil)) {
            // imp取初始值forward_imp(错误提示),跳出for操作。
            imp = forward_imp;
            break;
        }

        // 防止无限循环
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }

        // 【进入循环】
        // cache_getImp走汇编,循环从父类缓存中高速寻找sel对应的Imp
        imp = cache_getImp(curClass, sel);
        
        // 如果imp是初始值forward_imp(错误提示),就跳出循环
        if (slowpath(imp == forward_imp)) {
            break;
        }
        
        // 在父类中找到imp后,跳到下面done继续操作
        if (fastpath(imp)) {
            goto done;
        }
    }
OC底层原理十三: objc_msgSend(方法慢速查找)_第7张图片
image.png

7.最后的防线

我们看到,上一步操作中,如果没找到imp,都会break跳出for操作,进入下面的动态尝试

  • 这是慢速查找最后的倔强,也是系统给予sel方法的最后一次补救机会。
// behavior 默认为3, LOOKUP_RESOLVER为2
 if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER; // behavior取LOOKUP_RESOLVER的反
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

这段代码只执行一次

    1. 首次进入:behavior=3, LOOKUP_RESOLVER = 2, behavior & LOOKUP_RESOLVER = 2 (二进制计算), if条件成立。
    1. behaviorLOOKUP_RESOLVER,下一次behaviorLOOKUP_RESOLVER进行与运算时,结果一定为0
      (比如 001 取反是 110, 他们每个位相反,所以与运算的结果为000
  • 所以这个补救机会,只会进入一次。

  • 这一步包括动态方法决议三步挽救法,我们在下一节详细讲解。

本节我们只需要知道,如果最后挽救失败,程序就会抛出forward_imp异常内容。并crash了。

8.查找结果

  • 当我们找到imp时,就会直接进入done流程,将clsselimp写入缓存中(便于下次同样的方法,可以在cache汇编快速查询到)。

  • done结束后,我们会进入done_nolock流程, 如果impforward_imp,就返回nil,否则,返回正常的imp

done:
    // 从将sel和imp写入cls的缓存
    log_and_fill_cache(cls, imp, sel, inst, curClass);
    // 运行时解锁
    runtimeLock.unlock();

 done_nolock:
    //如果不需要找了(LOOKUP_NIL),并且imp等于forward_imp,就返回nil。
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    // 返回当前获取的imp
    return imp;

下一节,OC底层原理十四: objc_msgSend (消息转发 + 汇总图),我们会探究最后的防线,并梳理objc_msgSend完整流程

你可能感兴趣的:(OC底层原理十三: objc_msgSend(方法慢速查找))