前提

代码演示的项目和方法的本质Demo结构预览展示的一样,直接参考。

代码演示方法查找

这个可以用代码来演示:

屏幕快照 2020-04-01 22.44.42.png

上面的代码中总结:
自己有直接找到,
自己没有去父类找,有直接找到,
自己没有、父类没有、就去父类的父类去找,直到找到NSObject,都没有找到,会造成崩溃。
比如序号1序号2,项目中并没有实现saySomething()方法,所以找不到,报错:
unrecognized selector sent to instance/class;报错的后面分析。
那么序号3是什么意思?Student类为什么能直接调用nsobjectA方法(这是个对象方法啊)?并且编译通过不崩溃。继续往下看

查找流程

上边代码的演示有些疑惑的地方,都在图里:红色表示实例方法查找流程,绿色表示类方法查找流程。
1584863466373-5b6fcd0d-8914-4d2b-9641-dcce1e7afa61.png
实例方法存在类中,类方法存在元类中。调用方法就按照这个图,实例方法在类中依次向父类中查找,找到则已,找不到的话(找到nil为止终止循环),如果没有实现消息转发的话,就会崩溃;类方法在元类中查找同上,但是有一个点需要注意,绿色框的位置,根元类的父类是NSObject,这也是为什么代码演示序号3中,Student类能调用nsobjectA这个实例方法的原因。

分析源码

方法的本质这篇博客中了解到,方法调用,在底层编译就是objc_msgSend,查找方法先通过汇编到cache中快速查找,如果找不到,就会会调到C语言函数_class_lookupMethodAndLoadCache3开始慢速查找,接下来就开始分析慢速查找的源码逻辑。

_class_lookupMethodAndLoadCache3

  1. /***********************************************************************
  2. * _class_lookupMethodAndLoadCache.
  3. * Method lookup for dispatchers ONLY. OTHER CODE SHOULD USE lookUpImp().
  4. * This lookup avoids optimistic cache scan because the dispatcher
  5. * already tried that.
  6. **********************************************************************/
  7. IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
  8. {
  9. return lookUpImpOrForward(cls, sel, obj,
  10. YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
  11. }

_class_lookupMethodAndLoadCache3方法内直接调用的lookUpImpOrForward方法,项目演示看一下这三个参数:
屏幕快照 2020-04-02 11.04.03.png
student调用studentA()方法,第一次调用,cache中必然找不到,就需要走到慢速查找的路径来直接在_class_lookupMethodAndLoadCache3方法中打断点追踪。
屏幕快照 2020-04-02 11.04.32.png
一目了然:obj就是指针student;sel就是调用的方法studentA;cls就是Student类。
然后继续走进lookUpImpOrForward分析,后边的参数中cache为NO。

lookUpImpOrForward源码

**
我这里直接粘贴上源码,源码上已经写好分析的注释:

  1. /***********************************************************************
  2. * lookUpImpOrForward.
  3. * The standard IMP lookup.
  4. * initialize==NO tries to avoid +initialize (but sometimes fails)
  5. * cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
  6. * Most callers should use initialize==YES and cache==YES.
  7. * inst is an instance of cls or a subclass thereof, or nil if none is known.
  8. * If cls is an un-initialized metaclass then a non-nil inst is faster.
  9. * May return _objc_msgForward_impcache. IMPs destined for external use
  10. * must be converted to _objc_msgForward or _objc_msgForward_stret.
  11. * If you don't want forwarding at all, use lookUpImpOrNil() instead.
  12. **********************************************************************/
  13. IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
  14. bool initialize, bool cache, bool resolver)
  15. {
  16. IMP imp = nil;
  17. bool triedResolver = NO;
  18. runtimeLock.assertUnlocked();// 断言的不用管
  19. // Optimistic cache lookup
  20. if (cache) { // 如果cache为YES的话,就直接在cache中找,找到就返回
  21. imp = cache_getImp(cls, sel);
  22. if (imp) return imp;
  23. }
  24. // runtimeLock is held during isRealized and isInitialized checking
  25. // to prevent races against concurrent realization.
  26. // runtimeLock is held during method search to make
  27. // method-lookup + cache-fill atomic with respect to method addition.
  28. // Otherwise, a category could be added but ignored indefinitely because
  29. // the cache was re-filled with the old value after the cache flush on
  30. // behalf of the category.
  31. runtimeLock.lock();
  32. checkIsKnownClass(cls);
  33. if (!cls->isRealized()) {
  34. realizeClass(cls);
  35. }
  36. if (initialize && !cls->isInitialized()) {
  37. runtimeLock.unlock();
  38. _class_initialize (_class_getNonMetaClass(cls, inst));
  39. runtimeLock.lock();
  40. // If sel == initialize, _class_initialize will send +initialize and
  41. // then the messenger will send +initialize again after this
  42. // procedure finishes. Of course, if this is not being called
  43. // from the messenger then it won't happen. 2778172
  44. }
  45. // 上边的都是一些为了安全起见的判断和准备,下边开始imp查找的流程
  46. retry:
  47. runtimeLock.assertLocked();
  48. // Try this class's cache.
  49. imp = cache_getImp(cls, sel);// 现在缓存中找,找到直接goto done
  50. if (imp) goto done;
  51. // Try this class's method lists.
  52. // 在本类中的方法列表中查找,找到就goto done
  53. {
  54. Method meth = getMethodNoSuper_nolock(cls, sel);
  55. if (meth) {
  56. // 找到这个Method,将这个imp填充到缓存中
  57. log_and_fill_cache(cls, meth->imp, sel, inst, cls);
  58. imp = meth->imp;
  59. goto done;
  60. }
  61. }
  62. // Try superclass caches and method lists.
  63. // 如果在本类中查找不到,就开启循环在父类(及父类的父类)中进行查找
  64. // 在父类中查找也是优先看缓存,缓存中没有再去method list中查找
  65. // 同样找到之后会将这个方法在cls中缓存,注意的是,这个cls是外界传进来的并不是循环中的父类
  66. {
  67. unsigned attempts = unreasonableClassCount();
  68. for (Class curClass = cls->superclass;
  69. curClass != nil;
  70. curClass = curClass->superclass)
  71. {
  72. // Halt if there is a cycle in the superclass chain.
  73. if (--attempts == 0) {
  74. _objc_fatal("Memory corruption in class list.");
  75. }
  76. // Superclass cache.
  77. imp = cache_getImp(curClass, sel);
  78. if (imp) {
  79. if (imp != (IMP)_objc_msgForward_impcache) {
  80. // Found the method in a superclass. Cache it in this class.
  81. log_and_fill_cache(cls, imp, sel, inst, curClass);
  82. goto done;
  83. }
  84. else {
  85. // Found a forward:: entry in a superclass.
  86. // Stop searching, but don't cache yet; call method
  87. // resolver for this class first.
  88. break;
  89. }
  90. }
  91. // Superclass method list.
  92. // 在curClass(父类)中的方法列表中查找,找到就goto done
  93. Method meth = getMethodNoSuper_nolock(curClass, sel);
  94. if (meth) {
  95. log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
  96. imp = meth->imp;
  97. goto done;
  98. }
  99. }
  100. }
  101. // No implementation found. Try method resolver once.
  102. // 没有找到的话,尝试一下_class_resolveMethod处理
  103. if (resolver && !triedResolver) {
  104. runtimeLock.unlock();
  105. _class_resolveMethod(cls, sel, inst);
  106. runtimeLock.lock();
  107. // Don't cache the result; we don't hold the lock so it may have
  108. // changed already. Re-do the search from scratch instead.
  109. triedResolver = YES;
  110. goto retry;
  111. }
  112. // No implementation found, and method resolver didn't help.
  113. // Use forwarding.
  114. // 没找到,动态方法解析拯救不了,开始消息转发
  115. imp = (IMP)_objc_msgForward_impcache;
  116. cache_fill(cls, sel, imp, inst);
  117. done:
  118. runtimeLock.unlock();
  119. return imp;
  120. }

方法查找的流程在那个经典的图中已经很清晰,上边的源码,就是逻辑的代码实现。具体的细节可以lldb跟一下源码就可以了。

总结

方法的查找流程分为快速查找慢速查找,快速查找就是通过汇编在缓存中查找,如果找不到,开始慢速查找,在类及其父类的method list中向上循环查找。
在Demo中,如果都找不到的话,系统就会崩溃,那么这种未找到方法的崩溃,苹果有没有给开发者一些补救的机会呢?下一篇消息转发机制中继续分析。