Objective-C 消息发送与转发机制原理

objc_msgSend,它的伪代码如下或类似的逻辑,获取 IMP 并调用:

id objc_msgSend(id self, SEL _cmd, ...) {
    Class class = object_getClass(self);
    IMP imp = class_getMethodImplementation(class, _cmd);
    return imp ? imp(self, _cmd, ...) : 0;
}

class_getMethodImplementation 的实现如下:

IMP class_getMethodImplementation(Class cls, SEL sel)
{
    IMP imp;
    if (!cls  ||  !sel) return nil;
    imp = lookUpImpOrNil(cls, sel, nil, YES/*initialize*/, YES/*cache*/, YES/*resolver*/);
    // Translate forwarding function to C-callable external version
    if (!imp) {
        return _objc_msgForward;
    }
    return imp;
}

lookUpImpOrNil 的实现如下:

IMP lookUpImpOrNil(Class cls, SEL sel, id inst, 
                   bool initialize, bool cache, bool resolver)
{
    IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
    if (imp == _objc_msgForward_impcache) return nil;
    else return imp;
}

使用 lookUpImpOrForward 快速查找 IMP

lookUpImpOrForward 的实现见 objc-runtime-new.mm 4587行,大致逻辑如下:

  1. 如果 selector 是需要被忽略的垃圾回收用到的方法,则将 IMP 结果设为 _objc_ignored_method,这是个汇编程序入口,可以理解为一个标记。对此种情况进行缓存填充操作后,跳到第 7 步;否则执行下一步。
  2. 查找当前类中的缓存,跟之前一样,使用 cache_getImp 汇编程序入口。如果命中缓存获取到了 IMP,则直接跳到第 7 步;否则执行下一步。
  3. 在当前类中的方法列表(method list)中进行查找,也就是根据 selector 查找到 Method 后,获取 Method 中的 IMP(也就是 method_imp 属性),并填充到缓存中。查找过程比较复杂,会针对已经排序的列表使用二分法查找,未排序的列表则是线性遍历。如果成功查找到 Method 对象,就直接跳到第 7 步;否则执行下一步。
  4. 在继承层级中递归向父类中查找,情况跟上一步类似,也是先查找缓存,缓存没中就查找方法列表。这里跟上一步不同的地方在于缓存策略,有个 _objc_msgForward_impcache 汇编程序入口作为缓存中消息转发的标记。也就是说如果在缓存中找到了 IMP,但如果发现其内容是 _objc_msgForward_impcache,那就终止在类的继承层级中递归查找,进入下一步;否则跳到第 7 步。
  5. 当传入 lookUpImpOrForward 的参数 resolver 为 YES 并且是第一次进入第 5 步时,时进入动态方法解析;否则进入下一步。这步消息转发前的最后一次机会。此时释放读入锁(runtimeLock.unlockRead()),接着间接地发送 +resolveInstanceMethod 或 +resolveClassMethod 消息。这相当于告诉程序员『赶紧用 Runtime 给类里这个 selector 弄个对应的 IMP 吧』,因为此时锁已经 unlock 了所以不会缓存结果,甚至还需要软性地处理缓存过期问题可能带来的错误。这里的业务逻辑稍微复杂些,后面会总结。因为这些工作都是在非线程安全下进行的,完成后需要回到第 1 步再次查找 IMP。
  6. 此时不仅没查找到 IMP,动态方法解析也不奏效,只能将 _objc_msgForward_impcache 当做 IMP 并写入缓存。这也就是之前第 4 步中为何查找到 _objc_msgForward_impcache 就表明了要进入消息转发了。
  7. 读操作解锁,并将之前找到的 IMP 返回。(无论是正经 IMP 还是不正经的 _objc_msgForward_impcache

objc_msgForward_impcache 的转换

上一节最后讲到如果没找到 IMP,就会将 _objc_msgForward_impcache 返回到 objc_msgSend 函数,而正是因为它是用汇编语言写的,所以将内部使用的 _objc_msgForward_impcache 转化成外部可调用的 _objc_msgForward_objc_msgForward_stret 也是由汇编代码来完成。
_objc_msgForward* 系列本质都是函数指针,都用汇编语言实现,都可以与 IMP 类型的值作比较。

objc_msgForward 也只是个入口

从汇编源码可以看出 _objc_msgForward_objc_msgForward_stret 会分别调用 _objc_forward_handler_objc_forward_handler_stret

也就是说,消息转发过程是现将 _objc_msgForward_impcache 强转成 _objc_msgForward_objc_msgForward_stret,再分别调用 _objc_forward_handler_objc_forward_handler_stret

在 Objective-C 2.0 之前,默认的 _objc_forward_handler_objc_forward_handler_stret 都是 nil,而新版本的默认实现是这样的:

// Default forward handler halts the process.
__attribute__((noreturn)) void 
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);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;

#if SUPPORT_STRET
struct stret { int i[100]; };
__attribute__((noreturn)) struct stret 
objc_defaultForwardStretHandler(id self, SEL sel)
{
    objc_defaultForwardHandler(self, sel);
}
void *_objc_forward_stret_handler = (void*)objc_defaultForwardStretHandler;
#endif

objc_defaultForwardHandler 中的 _objc_fatal 作用就是打日志并调用 __builtin_trap() 触发 crash,可以看到我们最熟悉的那句 “unrecognized selector sent to instance” 日志。__builtin_trap() 在杀掉进程的同时还能生成日志,比调用 exit() 更好。

因为默认的 Handler 干的事儿就是打日志触发 crash,我们想要实现消息转发,就需要替换掉 Handler 并赋值给 _objc_forward_handler_objc_forward_handler_stret,赋值的过程就需要用到 objc_setForwardHandler 函数,实现也是简单粗暴,就是赋值啊:

void objc_setForwardHandler(void *fwd, void *fwd_stret)
{
    _objc_forward_handler = fwd;
#if SUPPORT_STRET
    _objc_forward_stret_handler = fwd_stret;
#endif
}

重头戏在于对 objc_setForwardHandler 的调用,以及之后的消息转发调用栈。这里涉及汇编及反编译,不细说这个,想看的可以在参考资料里面去细看。这里大概逻辑就是CoreFoundation会调用objc_setForwardHandler设置handle,设置的handle会调用___forwarding______forwarding___函数的伪代码实现如下:

int __forwarding__(void *frameStackPointer, int isStret) {
  id receiver = *(id *)frameStackPointer;
  SEL sel = *(SEL *)(frameStackPointer + 8);
  const char *selName = sel_getName(sel);
  Class receiverClass = object_getClass(receiver);

  // 调用 forwardingTargetForSelector:
  if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
    id forwardingTarget = [receiver forwardingTargetForSelector:sel];
    if (forwardingTarget && forwarding != receiver) {
        if (isStret == 1) {
            int ret;
            objc_msgSend_stret(&ret,forwardingTarget, sel, ...);
            return ret;
        }
      return objc_msgSend(forwardingTarget, sel, ...);
    }
  }

  // 僵尸对象
  const char *className = class_getName(receiverClass);
  const char *zombiePrefix = "_NSZombie_";
  size_t prefixLen = strlen(zombiePrefix); // 0xa
  if (strncmp(className, zombiePrefix, prefixLen) == 0) {
    CFLog(kCFLogLevelError,
          @"*** -[%s %s]: message sent to deallocated instance %p",
          className + prefixLen,
          selName,
          receiver);
    
  }

  // 调用 methodSignatureForSelector 获取方法签名后再调用 forwardInvocation
  if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
    NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
    if (methodSignature) {
      BOOL signatureIsStret = [methodSignature _frameDescriptor]->returnArgInfo.flags.isStruct;
      if (signatureIsStret != isStret) {
        CFLog(kCFLogLevelWarning ,
              @"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'.  Signature thinks it does%s return a struct, and compiler thinks it does%s.",
              selName,
              signatureIsStret ? "" : not,
              isStret ? "" : not);
      }
      if (class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
        NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];

        [receiver forwardInvocation:invocation];

        void *returnValue = NULL;
        [invocation getReturnValue:&value];
        return returnValue;
      } else {
        CFLog(kCFLogLevelWarning ,
              @"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message",
              receiver,
              className);
        return 0;
      }
    }
  }

  SEL *registeredSel = sel_getUid(selName);

  // selector 是否已经在 Runtime 注册过
  if (sel != registeredSel) {
    CFLog(kCFLogLevelWarning ,
          @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort",
          sel,
          selName,
          registeredSel);
  } // doesNotRecognizeSelector
  else if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
    [receiver doesNotRecognizeSelector:sel];
  } 
  else {
    CFLog(kCFLogLevelWarning ,
          @"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort",
          receiver,
          className);
  }

  // The point of no return.
  kill(getpid(), 9);
}

参考资料:

Objective-C 消息发送与转发机制原理

Objective-C 函数节流及函数防抖

你可能感兴趣的:(Objective-C 消息发送与转发机制原理)