消息查找流程

132 阅读8分钟

如何研究消息查找?

1.通过汇编代码进行跟踪。(需要在可运行源码中进行查看)


  • 创建对象,并进打断点在发送消息代码中。
LGPerson *person = [LGPerson alloc];
[person sayNB];
  • 查看汇编代码。
0x100000e70 <+64>:  callq  *0x18a(%rip)              ; 
(void *)0x00007fff66883040: objc_msgSend

断点打在objc_msgSend中,再进行断点跳转->

0x7fff66883103 <+195>: jmp    0x7fff66883b90            ; 
_objc_msgSend_uncached

因为是新创建的方法没有缓存,需要jmp跳转_objc_msgSend_uncached

0x1003430af <+175>: jmp    0x100343a50               ;
_objc_msgSend_uncached

继续跟进_objc_msgSend_uncached查看 ->

 0x100343a8f <+63>:  callq  0x100343f10               ; 
 ::_class_lookupMethodAndLoadCache3(id, SEL, Class) at objc-runtime-new.mm:4845

::代表是 C++代码,objc-runtime-new.mm表示在新的runtime代码中。_class_lookupMethodAndLoadCache3是进行方法查找和缓存加载。

继续查看 ->

0x100343f3d <+45>: callq  0x100364150               ; 
::lookUpImpOrForward(Class, SEL, id, bool, bool, bool) at objc-runtime-new.mm:4865

看到lookUpImpOrForward,就可以进行源码查看了。

2.源码查看


小知识点 - 忽略代码警告的宏定义

#pragma clang diagnostic push
// 让编译器忽略错误
#pragma clang diagnostic ignored "-Wundeclared-selector"
中间添加代码
#pragma clang diagnostic pop

  • 前面准备条件 继承关系 LGStudent -> LGPerson -> NSObject

LGStudent:

- (void)sayHello;
+ (void)sayObjc;

LGPerson:

- (void)sayNB;
+ (void)sayHappay;

NSObject+LG:

- (void)sayMaster;
+ (void)sayEasy;

  • 1.方法调用

    • 对象 方法调用情况

      • 创建LGStudent对象,调用自己的方法。
      LGStudent *student = [[LGStudent alloc] init];
      [student sayHello];
      
      • 自己没有,调用父类的。
      [student sayNB];
      
      • 自己没有,父类没有,调用NSObject的方法。
       [student sayMaster];
      
      • 自己、父类、NSObject都没有的方法
      [student performSelector:@selector(saySomething)];
      

      会爆出经典的bug: unrecognized selector sent to instance 0x103000450

    • 类 方法调用情况

      • 调用自己的方法。
      [LGStudent sayObjc];
      
      • 自己没有,调用父类的。
      [LGStudent sayHappay];
      
      • 自己没有,父类没有,调用NSObject的方法。
       [LGStudent sayEasy];
      
      • 自己、父类、NSObject都没有的方法
      [student performSelector:@selector(sayLove)];
      

      也会报出经典的bug: unrecognized selector sent to instance 0x103000450

      • 调用NSObject+LG中的sayMaster方法,即 类 调用 对象方法。
      [LGStudent performSelector:@selector(sayMaster)];
      

      结果是可以调用的,控制台打印-[NSObject(LG) sayMaster]

      原因:是按照isa的走位图进行方法寻找。

      类中查找实例方法 -> 元类中查找类方法 -> 根源类中查找元类的方法 -> NSObject中查找根源类的方法。

      在NSObject中的是实例方法,找到对应的方法会返回。

      NSObject中未找到的话,就会报错。


  • 2.方法慢速查找流程分析

    在快速查找方法中,没有查找到,才会找到_class_lookupMethodAndLoadCache3方法,进行慢速查找。

    在对汇编代码跟中得知,最后方法查找到的是_class_lookupMethodAndLoadCache3方法,下面进行源码查看。

    • 方法查找流程

      • 搜索到_class_lookupMethodAndLoadCache3方法,extern是为了外部可以调用到此函数
      extern IMP _class_lookupMethodAndLoadCache3(id, SEL, Class);
      
      IMP _class_lookupMethodAndLoadCache3(id obj(对象), SEL sel(方法名), Class cls(类))
          {
              return lookUpImpOrForward(cls, sel, obj, 
                            YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
          }
      
      
      • lookUpImpOrForward查看(查看重点代码)并进行代码段分析。

        • cache查询

        进行cache查询,说明此接口可以在多处调用,有cache时,进行imp的获取并返回。

        if (cache) {
                imp = cache_getImp(cls, sel);
                if (imp) return imp;
            }
        
        • runtimeLock.lock();进行上锁操作,防止多个方法进行查找、返回时出现返回错误。
        • checkIsKnownClass(cls);类进行判断,未被编译器加载,则返回一个未知的结果。
        • 判断类是否实现,实现直接返回该类,查看realizeClass方法,发现是查找rwro中属性、方法等的实现情况,是为了下面的函数执行做准备。
        if (!cls->isRealized()) {
            realizeClass(cls);
        }
        
        • 判断时候进行了初始化。
        if (initialize  &&  !cls->isInitialized()) {
        ...
        }
        

        上面这些其实是在为慢速查找做的准备。

      • 查找,在retry中进行分情况进行查找。

        • 类 的缓存中查找

          // Try this class's cache.
          imp = cache_getImp(cls, sel);
          if (imp) goto done;
          
        • 类 的方法列表中查找,此查找中二分法查找

          如果查找到缓存,则调用log_and_fill_cache函数进行缓存

          // Try this class's method lists.
          {
              Method meth = getMethodNoSuper_nolock(cls, sel);
              if (meth) {
                  log_and_fill_cache(cls, meth->imp, sel, inst, cls);
                  imp = meth->imp;
                  goto done;
              }
          }
          
        • 父类 的缓存和方法列表中查找

          // Try superclass caches and method lists.
          {
           unsigned attempts = unreasonableClassCount();
              父类的元类 = 元类的父类
              for (Class curClass = cls->superclass;
                  curClass != nil;
                  curClass = curClass->superclass)
           {
                  // Halt if there is a cycle in the superclass chain.
                  if (--attempts == 0) {
                      _objc_fatal("Memory corruption in class list.");
                  }
              
              父类缓存
              // Superclass cache.
                  imp = cache_getImp(curClass, sel);
                  if (imp) {
                  if (imp != (IMP)_objc_msgForward_impcache) {
                       // Found the method in a superclass. Cache it in this class.
                          log_and_fill_cache(cls, imp, sel, inst, curClass);
                      goto done;
                      }
                  else {
                          // Found a forward:: entry in a superclass.
                          // Stop searching, but do not cache yet; call method 
                          // resolver for this class first.
                          break;
                      }
                  }
                  父类 方法列表
                  // Superclass method list.
                  Method meth = getMethodNoSuper_nolock(curClass, sel);
                  if (meth) {
                      log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                      imp = meth->imp;
                      goto done;
                  }
              }
          }
          
        • 没有实现。尝试方法解析器一次。

          查看_class_resolveMethod的函数实现,发现先对元类判断,是不是元类则进行实力方法查找,是元类就进行类方法查找,着也验证了上面 类 调用NSObject的实例方法可以实现的原因。

          在消息转发中会对_class_resolveMethod进行解释。

          // No implementation found. Try method resolver once.
          if (resolver  &&  !triedResolver) {
              runtimeLock.unlock();
              _class_resolveMethod(cls, sel, inst);
              runtimeLock.lock();
              // Do not cache the result; we do not hold the lock so it may have 
              // changed already. Re-do the search from scratch instead.
              triedResolver = YES;
           goto retry;
          }
          
        • 没有找到实现,方法解析器也没有帮助。则使用转发机制。

          _objc_msgForward_impcache消息转发函数

          // No implementation found, and method resolver did not help. 
          // Use forwarding.
          imp = (IMP)_objc_msgForward_impcache;
          cache_fill(cls, sel, imp, inst);
          
        • 去掉线程锁

          done:
          runtimeLock.unlock();
          

      上面方法都为找到的情况下,会走这个方法_objc_msgForward_impcache

      • 查找_objc_msgForward_impcache方法。

           - 继续查找发现`_objc_msgForward_impcache`未实现。
        
          - 全局搜索也找到的是调用,没有找到实现。
        
          - 在汇编代码中发现了`STATIC_ENTRY __objc_msgForward_impcache` 
          ```
          STATIC_ENTRY __objc_msgForward_impcache
        
          // No stret specialization.
          调用 __objc_msgForward
          b	__objc_msgForward
        
          END_ENTRY __objc_msgForward_impcache
        
          进入__objc_msgForward
          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语言方法区别是 差一个`_`下划线,搜索`_objc_forward_handler`。
        
      • 在c语言中查找_objc_forward_handler方法。

        • 返现会被赋值objc_defaultForwardHandler函数
          void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
          
        • 查看函数。
           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);
          }
          
          "%c[%s %s]: unrecognized selector sent to instance %p " "(no message forward handler is installed)

      惊讶!原来系统的报错方法是在这里!!!

  • 消息转发

    其实在程序奔溃之前,系统会进行消息转发进行补救。

    方法查找流程分为 快速查找 、 慢速查找

    消息转发机制 也分为 快速转发 、 慢速转发

    在“方法的慢速查找”中,知道会有走一个方法 if (resolver && !triedResolver) {}方法。

    • 进入_class_resolveMethod方法。

      void _class_resolveMethod(Class cls, SEL sel, id inst)
      {
          if (! cls->isMetaClass()) {
           // try [cls resolveInstanceMethod:sel]
      
          _class_resolveInstanceMethod(cls, sel, inst);
      } 
      else {
          // try [nonMetaClass resolveClassMethod:sel]
          // and [cls resolveInstanceMethod:sel]
          _class_resolveClassMethod(cls, sel, inst);
          if (!lookUpImpOrNil(cls, sel, inst, 
                          NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
              {
                  _class_resolveInstanceMethod(cls, sel, inst);
              }
          }
      }
      

      if (! cls->isMetaClass()) {}是判断是否是元类,不是元类走的是对象方法,是元类走的类方法。

      • 类方法走_class_resolveClassMethod中的方法,会进行一个查找lookUpImpOrForward

        因为实例方法在类中,类方法在元类中。所以会先走else中的_class_resolveClassMethod函数

        static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
            {
                assert(cls->isMetaClass());
                if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, 
                     NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
                {
                    // Resolver not implemented.
                    return;
                }
                
                BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
                bool resolved = msg(_class_getNonMetaClass(cls, inst), 
                    SEL_resolveClassMethod, sel);
                ...
            }
        

        会进行一次lookUpImpOrNil查找,再走一次lookUpImpOrForward

      • _class_resolveClassMethod中进行一个查找之后未发现,就会走else中的lookUpImpOrNil方法。

        if (!lookUpImpOrNil(cls, sel, inst, 
                        NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
            {
                _class_resolveInstanceMethod(cls, sel, inst);
            }
        

        再次查找,为找到会进入到_class_resolveInstanceMethod方法。

      • _class_resolveInstanceMethod中走lookUpImpOrNil进行第二次查找。

        static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
            {
                if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                     NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
                    {
                        // Resolver not implemented.
                        return;
                    }
            ...
            }
        
      • 第二次查找未找到,会一直找到根源类,会走if (! cls->isMetaClass()) {}中的_class_resolveInstanceMethod函数进行第三次实例方法查找。

        if (! cls->isMetaClass()) {
            // try [cls resolveInstanceMethod:sel]
        
            _class_resolveInstanceMethod(cls, sel, inst);
        } 
        

      • _class_resolveClassMethod的三次查找中都未找到imp,则triedResolver会被标记成YES,再进行goto retry操作,重走一遍,但是不会进入到if (resolver && !triedResolver) {}方法中,并且会发送
      if (resolver  &&  !triedResolver) {
          methodListLock.unlock();
          _class_resolveMethod(cls, sel, inst);
          triedResolver = YES;
          goto retry;
      }
      
      • _class_resolveClassMethod中只要能找到imp,则会走goto done;
        done:
            methodListLock.unlock();
        
            return methodPC;
        

      以为着会后走报错的_cache_addForwardEntry函数。


    • 从上面的代码分析中可以得知_class_resolveInstanceMethod函数,会被多次调用,那么在外面合适的地方进行重写此方法,并进行处理,就不会出现崩溃显现。

      • 重写_class_resolveInstanceMethod进行 消息转发。
      + (BOOL)resolveInstanceMethod:(SEL)sel{
      
          if (sel == @selector(saySomething)) {
              NSLog(@"说话了");
              
              获取需要执行此转发任务方法的 imp
              IMP sayHIMP = class_getMethodImplementation(self, @selector(sayHello));
              
              获取需要执行此转发任务的 方法
              Method sayHMethod = class_getInstanceMethod(self, @selector(sayHello));
              
              需要的type类型
              const char *sayHType = method_getTypeEncoding(sayHMethod);
      
              return class_addMethod(self, sel, sayHIMP, sayHType);
          }
          NSLog(@"来了  老弟 - %p",sel);
          return [super resolveInstanceMethod:sel];
      }
      

      执行代码

      [student saySomething];

      执行结果

      2019-12-31 10:01:12.516446+0800 LGTest[1434:34340] 说话了
      2019-12-31 10:01:12.517101+0800 LGTest[1434:34340] -[LGStudent sayHello]
      Program ended with exit code: 0
      

    消息转发流程图: