objc_msgSend慢速查找流程

objc_msgSend慢速查找流程

当消息在cache里面找不到的时候会触发MissLabelDynamic,从代码CacheLookup NORMAL, _objc_msgSend, __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的实现

.macro MethodTableLookup


    // 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

    RESTORE_REGS MSGSEND

.endmacro

我们看到IMP在X0里面返回,说明_lookUpImpOrForward是关键信息,我们继续找_lookUpImpOrForward的实现

截屏2021-06-30 下午7.48.21.png

lookUpImpOrForward流程

1、先获取forward_imp,后面找不到的时候返回forward_imp

2、检查类是否已经被注册

3、对类进行第一次初始化,分配分配读写数据,返回类的真实结构

4、for循环查找,先查找此时缓存内是否有方法。如果存在通过cache_getImp()查找返回

5、查找当前类的method list找到的话就返回

6、如果还是找不到就curClass = curClass->getSuperclass()(一直找到NSObject)通过cache_getImp()去父类的缓存里面找

7、如果无论怎么都找不到,尝试方法解析器一次(单利)

8、如果在当中找到, goto done,返回imp并且调用log_and_fill_cache()缓存下来

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)

{
//获取forward_imp,当在类里面也找不到方法的时候返回forward_imp
  const IMP forward_imp = (IMP)_objc_msgForward_impcache;

  IMP imp = nil;

  Class curClass;
  
    //检车类是否已经被注册
    checkIsKnownClass(cls);
    
    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
    // runtimeLock may have been dropped but is now locked again
    runtimeLock.assertLocked();
    curClass = cls;

//一个for的死循环,如果在里面有goto或者break等的时候会跳出循环
  for (unsigned attempts = unreasonableClassCount();;) {

//防止此时方法被插进去,再做一个快速缓存查找
    if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
      imp = cache_getImp(curClass, sel);

      if (imp) goto done_unlock;

      curClass = curClass->cache.preoptFallbackClass();


    } else {

      //缓存中没有,去method list.里面查找
      //先for循环,后面会进入二分法查找流程
      Method meth = getMethodNoSuper_nolock(curClass, sel);

      if (meth) {//找到之后返回

        imp = meth->imp(false);
       
        goto done;//具体走log_and_fill_cache,再次insert插入缓存

      }

     //上面去自己的methodlist找不到,就去获取父类的里面找->父类的父类找->找到NSObject
     //如果还找不到,返回forward_imp
     if (slowpath((curClass = curClass->getSuperclass()) == nil)) {

        imp = forward_imp;

        break;

      }

    }

    // 父类缓存查找,找不到返回nil
    //curClass = curClass->getSuperclass()不断找父类的过程
    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;

}

findMethodInSortedMethodList()实现

在查找过程中会调用findMethodInSortedMethodList()方法,我们分析一下算法实现过程(二分法)

假设count = 8,我们要找的数在第6位(可以任意,这里举例说明)

1、count >>= 1count按位右移,相当于➗2

2、probe = base + (count >> 1) ,相当于probe=0(base)+4(count)=4

3、如果6>4,base = probe + 1``count--,相当于base=4+1=5,count=7

4、再次进入for循环count=3, probe= 5+(3>>1)= 5+1=6,此时命中(keyValue == probeValue)

5、while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) { probe--;}这个意思就是有多个相同的方法时,返回最前面的方法。其实就是返回分类的方法。如果有多个分类会返回最后编译进去的分类。编译顺序可以在Compile Sources中调整测试。

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) {
   //返回分类的方法
      while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {

        probe--;

      }

     return &*probe;

    }

    if (keyValue > probeValue) {

      base = probe + 1;

      count--;

    }
  }

  return nil;

}


类方法和实例方法

我们新建一个NSObject的分类NSObject+EL我们在里面实现两个方法

- (void)sayNB{

  NSLog(@"NSObject===%s",__func__);

}

+ (void)sayHello{

  NSLog(@"NSObject===%s",__func__);

}

新建一个ELPerson类继承于NSObject,我们直接调用如下代码

int main(int argc, const char * argv[]) {

  @autoreleasepool {

    ELPerson *p = [ELPerson alloc];

    [ELPerson performSelector:@selector(sayHello)];

    [ELPerson performSelector:@selector(sayNB)];

    NSLog(@"Hello, World!");

  }

  return 0;

}
2021-07-01 11:31:23.567714+0800 KCObjcBuild[70678:3948791] NSObject===+[NSObject(EL) sayHello]

2021-07-01 11:31:23.568099+0800 KCObjcBuild[70678:3948791] NSObject===-[NSObject(EL) sayNB]

可以看出来两个都调用成功了,在oc底层其实没有“+”“-”方法的区分,只是方法存储的位置不同而已。我们具体分析下调用成功的流程:

1、NSObject中sayHello是类方法,在NSObject的元类(Root Class Meta)中存储,ELPerson调用sayHello方法,会从ELPerson的元类中找,找不到的话。从元类的父类找(Super Class Meta),继续找根元类(Root Class Meta),此时找到imp,返回。

2、NSObject中sayNB的实例方法,在NSObject的类中存储,ELPerson调用sayNB,会从ELPerson的元类中找,找不到的话。从元类的父类找(Super Class Meta),继续找根元类(Root Class Meta),再继续找(Root Class class)找到NSObject,此时找到imp,返回。

我们再进一步,用实例对象p调用看看回事什么结果

int main(int argc, const char * argv[]) {

  @autoreleasepool {

    ELPerson *p = [ELPerson alloc];

    //[p performSelector:@selector(sayHello)];

    [p performSelector:@selector(sayNB)];

    NSLog(@"Hello, World!");

  }

  return 0;

}
我们发现sayNB调用成功了,sayHello直接崩溃,找不到方法

具体分析一下调用流程:

3、sayNB调用成功,我们很好理解。直接ELPerson继承于NSObject,ELPerson里面没有实现,直接找到父类的实现。

4、sayHello没有调用成功,我们前面分析过sayHello存在NSObject的元类(Root Class Meta)当中,对象去调用方法的时候,先查找ELPerson类中查找,此时没有,再往父类(NSObject)中查找,我们发现此时也没有。再找NSObject的父类就是nil了,所以查找不到。因为这个流程并没有查找到NSObject的元类(Root Class Meta)当中去,所以找不到。

附isa的走位图,可以帮助我们分析:

image
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,928评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,192评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,468评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,186评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,295评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,374评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,403评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,186评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,610评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,906评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,075评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,755评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,393评论 3 320
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,079评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,313评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,934评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,963评论 2 351

推荐阅读更多精彩内容