美文网首页
Objective-C 消息发送与转发机制原理

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

作者: whlpkk | 来源:发表于2018-01-02 18:25 被阅读16次

    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);
        <breakpoint-interrupt>
      }
    
      // 调用 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 消息发送与转发机制原理

          本文链接:https://www.haomeiwen.com/subject/nxyqnxtx.html