iOS 全网最新objc4 可调式/编译源码
编译好的源码的下载地址

序言

在前一篇iOS底层之Runtime探索(一)中,已经知道了在selimp的整个缓存查找进程,这个进程是用汇编完结,是一个快速办法查找流程,今天就来探求一下缓存没有查找到后边的流程。

__objc_msgSend_uncached 办法探求

前面流程是对缓存进行查找imp,假如找不到就走办法__objc_msgSend_uncached办法

iOS底层之Runtime探索(二)
__objc_msgSend_uncached中,共两条句子MethodTableLookupTailCallFunctionPointer x17

MethodTableLookup办法解析

iOS底层之Runtime探索(二)
MethodTableLookup中的代码逻辑

  • x0receiverx1sel
  • mov x2, x16: 将x16也便是clsx2
  • mov x3, #3: 将常量值3x3
  • 调用_lookUpImpOrForward: x0~x3是传的参数;
  • mov x17, x0: 将_lookUpImpOrForward的回来值x0x17

经过注释也能够大致看出,调用办法lookUpImpOrForward获取imp,并回来。

TailCallFunctionPointer界说

iOS底层之Runtime探索(二)
这儿仅仅是对传入的$0直接调用。

咱们能够总结,__objc_msgSend_uncached办法流程是经过MethodTableLookup获取imp,然后传值到TailCallFunctionPointer界说调用imp

这儿的核心便是MethodTableLookup中的lookUpImpOrForward是怎样获取的imp

lookUpImpOrForward办法探求

lookUpImpOrForward经过办法命名,能够看出一些端倪,便是查找imp找不到就forward操作。

实例剖析

  • 对上面界说的LGPerson添加分类,并在分类中相同完结sayHello办法;
  • NSObject添加分类,并自界说办法sayNB
  • LGTeacher界说办法sayByeBye,但不添加完结;

iOS底层之Runtime探索(二)
结果

  • sayHello调用的是LGPerson的分类办法,why?
  • 用类方针LGTeacher调用NSObjectsayNB办法成功,why?
  • 未完结办法sayByeBye报错unrecognized selector sent to instance 0x600000008000,why?

lookUpImpOrForward源码剖析

源代码添加了中文注释

NEVER_INLINE
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())) {
    /*
    enum {
      LOOKUP_INITIALIZE = 1,
      LOOKUP_RESOLVER = 2,
      LOOKUP_NIL = 4,
      LOOKUP_NOCACHE = 8,
    };
    */
    // 假如没有初始化 behavior = LOOKUP_INITIALIZE | LOOKUP_RESOLVER | LOOKUP_NOCACHE
    behavior |= LOOKUP_NOCACHE;
  }
  // 加锁避免多线程拜访呈现紊乱
  runtimeLock.lock();
  // 查看当时类是否被dyld加载的类
  checkIsKnownClass(cls);
  // 假如没有完结给定的类,则完结该类;假如没有初始化,则初始化该类。
  cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
  runtimeLock.assertLocked();
  curClass = cls;
  //在咱们获取锁后,用于再次查找类缓存的代码,但关于大多数情况,证据标明这在大多数情况下都是失利的,因此会形成时刻丢失。
  //在没有履行某种缓存查找的情况下,唯一调用此函数的代码路径是class_getInstanceMethod()。
  for (unsigned attempts = unreasonableClassCount();;) {// 开启循环查找imp
    // 持续查看同享缓存是否有办法
    if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
      imp = cache_getImp(curClass, sel); // cache_getImp流程获取imp
      if (imp) goto done_unlock; // 找到imp走”done_unlock“流程
      curClass = curClass->cache.preoptFallbackClass();
#endif
    } else {
      // 从curClass的method list中查找method.
      method_t *meth = getMethodNoSuper_nolock(curClass, sel);
      if (meth) {
        imp = meth->imp(false);
        goto done; // 查找到imp,跳转done流程
      }
      // 未从curClass的method list中查找到对应的method,持续往父类查找,直到父类为nil
      if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
        // No implementation found, and method resolver didn't help.
        // Use forwarding.
        imp = forward_imp; // 查找不到跳出for循环
        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); // 这儿curClass已指向父类,查找父类的缓存中的imp
    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)) { // 从父类缓存中查找到imp,跳转到done
      // Found the method in a superclass. Cache it in this class.
      goto done;
    }
  }
  // No implementation found. Try method resolver once.
  // 为找到imp,开始resolveMethod_locked流程,动态办法抉择
  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); // 将获取的imp刺进缓存
  }
done_unlock:
  runtimeLock.unlock(); //runtimeLock 解锁
  if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
    return nil;
  }
  return imp;
}

逻辑流程剖析

  1. 判别当时类是否初始化,若未初始化履行behavior |= LOOKUP_NOCACHE
  2. checkIsKnownClass(cls)查看当时类是否被加载到dyld,类的加载归于懒加载,未加载这儿会做加载处理;
  3. realizeAndInitializeIfNeeded_locked假如没有完结给定的类,则完结该类;假如没有初始化,则初始化该类;
  4. 进入for循环开始一层层遍历查找imp;
  5. curClass->cache.isConstantOptimizedCache查看同享缓存,这个时分,或许其他地方进行缓存了,假如有则直接跳转done_unlock回来imp
  6. 上面没有缓存,getMethodNoSuper_nolock从当时类的methodlist中查找method
  7. 假如找到跳转done,将找到的imp进行缓存刺进,再回来imp
  8. 假如未找到,if (slowpath((curClass = curClass->getSuperclass()) == nil))这儿将curClass指向父类,并判别假如父类为nil,将imp指向forward_imp,break跳出for循环;
  9. 假如父类存在,经过cache_getImp查看父类缓存是否有imp,假如impforward_imp则跳出循环,然后再查看imp,假如imp有用则跳转done流程;
  10. 查找for循环结束,没有找到imp,按LOOKUP_RESOLVER判别走动态办法抉择流程resolveMethod_locked

归总剖析便是,音讯查找会沿着继承链一层一层往上查找,直到找到nil,假如有找到则刺进缓存并回来imp,假如找不到则imp指向forward_imp也便是_objc_msgForward_impcache

上面LGTeacher调用NSObject的分类办法sayNB的进程,便是LGTeacher沿着元类的继承链找到了NSObject,并调用sayNB办法。

lookUpImpOrForward流程图

iOS底层之Runtime探索(二)

getMethodNoSuper_nolock办法查找解析

getMethodNoSuper_nolock的办法中,查找method算法便是findMethodInSortedMethodList办法中的二分查找算法完结的。

template<class getNameFunc>
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
  ASSERT(list);
  // method_list_t 数组是经过将sel转为uintptr_t类型的value值进行排序的
  auto first = list->begin(); // 起始位置 0
  auto base = first;
  decltype(first) probe;
  uintptr_t keyValue = (uintptr_t)key; // 方针sel
  uint32_t count; // 数组个数
  for (count = list->count; count != 0; count >>= 1) {
    probe = base + (count >> 1); // probe = base + floor(count / 2)
    uintptr_t probeValue = (uintptr_t)getName(probe)
    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) { // 方针value大于,二分法的中间数
      base = probe + 1;
      count--;
    }
  }
  return nil;
}

这儿的二分算法设计仍是挺巧妙的,能够慢慢品玩;其中在keyValue == probeValue的时分,会进入while循环,让probe--,直到找到同名办法中最前面的办法,同名办法便是咱们分类中重写的办法,分类中办法会放在前面,所以调用是会优先调用分类中的办法完结。

cache_getImp办法解析

iOS底层之Runtime探索(二)
根据之前对CacheLookup的剖析,cache_getImpMissLabelDynamic传的是LGetImpMissDynamic,因此假如CacheLookup中找不到imp就会进入LGetImpMissDynamic,这儿仅仅是回来了0值,所以cache_getImp流程在这儿也就断了回来的是0

forward_imp解析

在取父类(curClass = curClass->getSuperclass()) == nil)的判别中,假如往上没有父类了,即条件成立,那imp会指向_objc_msgForward_impcache类型的forward_imp,并回来履行imp

iOS底层之Runtime探索(二)

  • _objc_msgForward_impcache中仅仅对__objc_msgForward进行调用;
  • __objc_msgForward是对__objc_forward_handler相关的办法操作回来值到x17,并经过TailCallFunctionPointer调用x17

iOS底层之Runtime探索(二)
__objc_forward_handler便是函数objc_defaultForwardHandler,这儿面便是直接抛出过错unrecognized selector sent to instance

这儿的过错信息字符串中,对+-的处理是经过元类判其他,底层中没有+号办法或-号办法,都是OC的语法设计。

总结

  • 在缓存cache中没有找到imp,就会经过lookUpImpOrForward开启从当时类NSObject办法列表进行层层遍历,这个进程为“慢速查找”进程;
  • 假如找到了就会回来imp,并履行;
  • 假如找不到就会让imp指向forward_imp并回来,履行forward_imp体系会直接抛出办法未找到的过错;
  • 在回来forward_imp之前,还有一步resolveMethod_locked操作,这便是动态办法抉择的流程,鄙人一篇幅展开细讲。

以上是Runtime中对objc_msgSned办法的慢速查找流程解析,如有疑问或过错之处,请在评论区留言或私信我,感谢支持~❤