Runtime-objc_msgSend源码

/** 
 * Sends a message with a simple return value to an instance of a class.
 * 
 * @param self A pointer to the instance of the class that is to receive the message.
 * @param op The selector of the method that handles the message.
 * @param ... 
 *   A variable argument list containing the arguments to the method.
 * 
 * @return The return value of the method.
 * 
 * @note When it encounters a method call, the compiler generates a call to one of the
 *  functions \c objc_msgSend, \c objc_msgSend_stret, \c objc_msgSendSuper, or \c objc_msgSendSuper_stret.
 *  Messages sent to an object’s superclass (using the \c super keyword) are sent using \c objc_msgSendSuper; 
 *  other messages are sent using \c objc_msgSend. Methods that have data structures as return values
 *  are sent using \c objc_msgSendSuper_stret and \c objc_msgSend_stret.
 */
OBJC_EXPORT id _Nullable
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)

SEL如下
typedef struct objc_selector *SEL;

第一个参数是receiver,是一个结构体,拥有isa指针的对象

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

第二个参数是具体的方法名字,映射到方法的C字符串。需要注意的是@selector()选择子只与函数名有关。不同类中相同名字的方法所对应的方法选择器是相同的,即使方法名字相同而变量类型不同也会导致它们具有相同的方法选择器。

receiver收到selector之后现在自己的方法中查找,如果没有就去父类查找,一直往上查找。


image.png

如果全都没有的话就会去做消息转发。


image.png

查不到的话就崩溃了。

那么objc_msgSend是怎么实现的呢?

在Obj-C Optimization: The faster objc_msgSend中提到了objc_msgSend是如何实现的。

#include 

id  c_objc_msgSend( struct objc_class /* ahem */ *self, SEL _cmd, ...)
{
   struct objc_class    *cls; 
   struct objc_cache    *cache;
   unsigned int         hash;
   struct objc_method   *method;   
   unsigned int         index;
   
// 当self存在的时候将self结构体的值取出来
   if( self)
   {
      cls   = self->isa;
      cache = cls->cache;
      hash  = cache->mask;
      index = (unsigned int) _cmd & hash;
      
      do
      {
         method = cache->buckets[ index];
         if( ! method)
// 汇编goto方法,直接跳到某一行继续执行
            goto recache;
         index = (index + 1) & cache->mask;
      }
      while( method->method_name != _cmd);
      return( (*method->method_imp)( (id) self, _cmd));
   }
   return( (id) self);

recache:
   /* ... */
   return( 0);
}

方法里面最重要的是执行了一个dowhile 循环,循环查找方法是否存在。

index = (unsigned int) _cmd & hash;

SEL和IMP用映射表关联。SEL 通过hash加密之后得到他对应的IMP的地址。

接下来看Source Browser/ [objc4-680.tar.gz]

arm64:iPhone6s | iphone6s plus|iPhone6| iPhone6 plus|iPhone5S | iPad Air| iPad mini2(iPad mini with Retina Display)
armv7s:iPhone5|iPhone5C|iPad4(iPad with Retina Display)
armv7:iPhone4|iPhone4S|iPad|iPad2|iPad3(The New iPad)|iPad mini|iPod Touch 3G|iPod Touch4

i386是针对intel通用微处理器32位处理器
x86_64是针对x86架构的64位处理器

模拟器32位处理器测试需要i386架构,
模拟器64位处理器测试需要x86_64架构,
真机32位处理器需要armv7,或者armv7s架构,
真机64位处理器需要arm64架构。

查看objc-msg-x86_64.s 的汇编源码


/********************************************************************
 *
 * id objc_msgSend(id self, SEL _cmd,...);
 *
 ********************************************************************/
    
    .data
    .align 3
    .globl _objc_debug_taggedpointer_classes
_objc_debug_taggedpointer_classes:
    .fill 16, 8, 0

    ENTRY   _objc_msgSend
    MESSENGER_START

    NilTest NORMAL

    GetIsaFast NORMAL       // r11 = self->isa
    CacheLookup NORMAL      // calls IMP on success

    NilTestSupport  NORMAL

    GetIsaSupport   NORMAL

// cache miss: go search the method lists
LCacheMiss:
    // isa still in r11
    MethodTableLookup %a1, %a2  // r11 = IMP
    cmp %r11, %r11      // set eq (nonstret) for forwarding
    jmp *%r11           // goto *imp

    END_ENTRY   _objc_msgSend

    
    ENTRY _objc_msgSend_fixup
    int3
    END_ENTRY _objc_msgSend_fixup

    
    STATIC_ENTRY _objc_msgSend_fixedup
    // Load _cmd from the message_ref
    movq    8(%a2), %a2
    jmp _objc_msgSend
    END_ENTRY _objc_msgSend_fixedup

查看汇编的注释后将一个方法分成两个步骤来解析

第一步 查找self.isa的方法缓存,如果找到了就调用。没找到进入第二步
    .data
    .align 3
    .globl _objc_debug_taggedpointer_classes
_objc_debug_taggedpointer_classes:
    .fill 16, 8, 0

    ENTRY   _objc_msgSend
    MESSENGER_START

// 判空处理 ,如果为空的话返回
    NilTest NORMAL

// 快速获取Isa,并且放到r11寄存器上面
    GetIsaFast NORMAL       // r11 = self->isa
// 查找方法缓存,如果找到了就调用IMP
    CacheLookup NORMAL      // calls IMP on success

    NilTestSupport  NORMAL

    GetIsaSupport   NORMAL
比较重要的几个点
align 用来字节对齐
例如32 位的寄存器容量是 4 字节, 如果内存中的数据都按 4*n 字节对齐, 肯定会加快吞吐速度;
但事实并非如此, 不同大小的数据可能会让寄存器别别扭扭地去处理, 从而降低了运行速度!

如果使用对齐, 就会浪费掉一些内存空间; 其实这是一个需要权衡 "速度" 与 "内存" 得失的问题.
NilTest
/////////////////////////////////////////////////////////////////////
//
// NilTest return-type
//
// Takes:   $0 = NORMAL or FPRET or FP2RET or STRET
//      %a1 or %a2 (STRET) = receiver
//
// On exit:     Loads non-nil receiver in %a1 or %a2 (STRET), or returns zero.
//
// NilTestSupport return-type
//
// Takes:   $0 = NORMAL or FPRET or FP2RET or STRET
//      %a1 or %a2 (STRET) = receiver
//
// On exit:     Loads non-nil receiver in %a1 or %a2 (STRET), or returns zero.
//
/////////////////////////////////////////////////////////////////////
.macro NilTest
.if $0 == SUPER  ||  $0 == SUPER_STRET
    error super dispatch does not test for nil
.endif

.if $0 != STRET
    testq   %a1, %a1
.else
    testq   %a2, %a2
.endif
    PN
    jz  LNilTestSlow_f
.endmacro

这个方法用来做nil判断
objc_msgSend 对应 NORMAL
objc_msgSend_fpret 对应 NilTest FPRET
objc_msgSend_fp2ret  对应 NilTest FP2RET
objc_msgSend_stret 传入的参数是NilTest STRET

test属于逻辑运算指令
功能: 执行BIT与BIT之间的逻辑运算
测试(两操作数作与运算,仅修改标志位,不回送结果).
Test对两个参数(目标,源)执行AND逻辑操作,并根据结果设置标志寄存器,结果本身不会保存。TEST AX,BX 与 AND AX,BX 命令有相同效果 

/////////////////////////////////////////////////////////////////////
//
// CacheLookup  return-type, caller
//
// Locate the implementation for a class in a selector's method cache.
//
// Takes: 
//    $0 = NORMAL, FPRET, FP2RET, STRET, SUPER, SUPER_STRET, SUPER2, SUPER2_STRET, GETIMP
//    a2 or a3 (STRET) = selector a.k.a. cache
//    r11 = class to search
//
// On exit: r10 clobbered
//      (found) calls or returns IMP, eq/ne/r11 set for forwarding
//      (not found) jumps to LCacheMiss, class still in r11
//
/////////////////////////////////////////////////////////////////////
.macro  CacheLookup
// 判断传入的是否是STRET,SUPER_STRET,SUPER2_STRET类型 如果不是的话将_cmd给A2,不然给A3
.if $0 != STRET  &&  $0 != SUPER_STRET  &&  $0 != SUPER2_STRET
    movq    %a2, %r10       // r10 = _cmd
.else
    movq    %a3, %r10       // r10 = _cmd
.endif
// and 是将后边两个操作数按位求&,加l表示后边两个操作数是4个字节32bit的
// shl 逻辑移位
// add 添加偏移量
    andl    24(%r11), %r10d     // r10 = _cmd & class->cache.mask
    shlq    $$4, %r10       // r10 = offset = (_cmd & mask)<<4
    addq    16(%r11), %r10      // r10 = class->cache.buckets + offset

.if $0 != STRET  &&  $0 != SUPER_STRET  &&  $0 != SUPER2_STRET
// cmp 比较方法是否一致
    cmpq    (%r10), %a2     // if (bucket->sel != _cmd)
.else
    cmpq    (%r10), %a3     // if (bucket->sel != _cmd)
.endif
    jne     1f          //     scan more
    // CacheHit 之前必须始终不采取“jne”指令
    CacheHit $0         // call or return imp

1:
    // 循环在缓存中查找方法
    cmpq    $$0, (%r10)
    je  LCacheMiss_f        // if (bucket->sel == 0) cache miss
    cmpq    16(%r11), %r10
    je  3f          // if (bucket == cache->buckets) wrap

    subq    $$16, %r10      // bucket--
2:  
.if $0 != STRET  &&  $0 != SUPER_STRET  &&  $0 != SUPER2_STRET
    cmpq    (%r10), %a2     // if (bucket->sel != _cmd)
.else
    cmpq    (%r10), %a3     // if (bucket->sel != _cmd)
.endif
    jne     1b          //     scan more
    // CacheHit 之前必须始终不采取“jne”指令
    CacheHit $0         // call or return imp

3:
    // wrap
    movl    24(%r11), %r10d     // r10 = mask a.k.a. last bucket index
    shlq    $$4, %r10       // r10 = offset = mask<<4
    addq    16(%r11), %r10      // r10 = &cache->buckets[mask]
    jmp     2f

    // 克隆查找循环崩溃 而不是去挂起
1:
    // loop
    cmpq    $$0, (%r10)
    je  LCacheMiss_f        // if (bucket->sel == 0) cache miss
    cmpq    16(%r11), %r10
    je  3f          // if (bucket == cache->buckets) wrap

    subq    $$16, %r10      // bucket--
2:  
.if $0 != STRET  &&  $0 != SUPER_STRET  &&  $0 != SUPER2_STRET
    cmpq    (%r10), %a2     // if (bucket->sel != _cmd)
.else
    cmpq    (%r10), %a3     // if (bucket->sel != _cmd)
.endif
    jne     1b          //     scan more
    // CacheHit 之前必须始终不采取“jne”指令
    CacheHit $0         // call or return imp

3:
    // double wrap - busted
.if $0 == STRET  ||  $0 == SUPER_STRET  ||  $0 == SUPER2_STRET
    movq    %a2, %a1
    movq    %a3, %a2
.elseif $0 == GETIMP
    movq    $$0, %a1
.endif
                // a1 = receiver
                // a2 = SEL
    movq    %r11, %a3   // a3 = isa
.if $0 == GETIMP
    jmp _cache_getImp_corrupt_cache_error
.else
    jmp _objc_msgSend_corrupt_cache_error
.endif

.endmacro

第二步:在MethodTableLookup中查找

// 第一步没有找到对应的方法时去父类查找
// cache miss: go search the method lists
LCacheMiss:
    // isa still in r11
    MethodTableLookup %a1, %a2  // r11 = IMP
    cmp %r11, %r11      // set eq (nonstret) for forwarding
    jmp *%r11           // goto *imp

    END_ENTRY   _objc_msgSend

    
    ENTRY _objc_msgSend_fixup
    int3
    END_ENTRY _objc_msgSend_fixup

    
    STATIC_ENTRY _objc_msgSend_fixedup
    // Load _cmd from the message_ref
    movq    8(%a2), %a2
    jmp _objc_msgSend
    END_ENTRY _objc_msgSend_fixedup
比较重要的几个点
MethodTableLookup  把receiver, selector, class三个参数传给$0,$1,r11,然后调用
__class_lookupMethodAndLoadCache3函数,函数返回的IMP 从r11给到rax中,查找成功就去调用这个IMP。

/////////////////////////////////////////////////////////////////////
//
// MethodTableLookup classRegister, selectorRegister
//
// Takes:   $0 = class to search (a1 or a2 or r10 ONLY)
//      $1 = selector to search for (a2 or a3 ONLY)
//      r11 = class to search
//
// On exit: imp in %r11
//
/////////////////////////////////////////////////////////////////////
.macro MethodTableLookup

    MESSENGER_END_SLOW
    
    SaveRegisters

    // _class_lookupMethodAndLoadCache3(receiver, selector, class)

    movq    $0, %a1
    movq    $1, %a2
    movq    %r11, %a3
    call    __class_lookupMethodAndLoadCache3

    // IMP is now in %rax
    movq    %rax, %r11

    RestoreRegisters

.endmacro

调用了lookUpImpOrForward方法 ,在本类中查找方法,找不到之后去父类查找,全都查不到之后调用转发流程
/***********************************************************************
* lookUpImpOrForward.
* 标准查找
* initialize==NO 尝试避免使用 +initialize (但有时候失败)
* cache==NO 跳过乐观锁查找(但在别的地方还是用到缓存)
* 尽量使用 initialize==YES 和 cache==YES.
* inst 是 instance of cls 或者 a subclass , 或者 nil 如果没有已知.
*   如果 cls 是 un-initialized metaclass 那么查找一个 non-nil inst 是非常快的。
* 可能返回 _objc_msgForward_impcache. IMPs 转换成 _objc_msgForward 或者 _objc_msgForward_stret.
*   如果你不想使用转发, 使用 lookUpImpOrNil()替代.
**********************************************************************/
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    Class curClass;
    IMP imp = nil;
    Method meth;
    bool triedResolver = NO;

    // 断言锁
    rwlock_assert_unlocked(&runtimeLock);

    // 乐观查找
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

    if (!cls->isRealized()) {
        // 没有找到的话
        // 加锁
        rwlock_write(&runtimeLock);
        // 申请class_rw_t空间。
        realizeClass(cls);
        // 解锁
        rwlock_unlock_write(&runtimeLock);
    }

    if (initialize  &&  !cls->isInitialized()) {
        // _class_initialize类初始化
        _class_initialize (_class_getNonMetaClass(cls, inst));
        // If sel == initialize, _class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }
    
 retry:
    // 添加一个读锁。防止动态添加方法,保证现场安全
    rwlock_read(&runtimeLock);

    // 忽略CG垃圾回收方法
    if (ignoreSelector(sel)) {
        imp = _objc_ignored_method;
        cache_fill(cls, sel, imp);
        goto done;
    }

    // 尝试class的cache中查找
    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // 尝试在methodlist中查找

    meth = getMethodNoSuper_nolock(cls, sel);
    if (meth) {
        log_and_fill_cache(cls, cls, meth->imp, sel);
        imp = meth->imp;
        goto done;
    }

    // 尝试superclass caches 和 method lists中查找

    curClass = cls;
    while ((curClass = curClass->superclass)) {
        // 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, curClass, imp, sel);
                goto done;
            }
            else {
                // Found a forward:: entry in a superclass.
                // Stop searching, but don't cache yet; call method 
                // resolver for this class first.
                break;
            }
        }

        // 获取超类方法列表
        meth = getMethodNoSuper_nolock(curClass, sel);
        if (meth) {
            log_and_fill_cache(cls, curClass, meth->imp, sel);
            imp = meth->imp;
            goto done;
        }
    }

    // No implementation found. Try method resolver once.
    // 找不到IMP,尝试调用_class_resolveMethod方法
    if (resolver  &&  !triedResolver) {
        rwlock_unlock_read(&runtimeLock);
        _class_resolveMethod(cls, sel, inst);
        // 不缓存结果,没有加锁所以他可以动态添加方法,添加完了之后重新走一次查找流程
        triedResolver = YES;
        goto retry;
    }

    // 找不到方法进入转发阶段

    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp);

 done:
    rwlock_unlock_read(&runtimeLock);

    // paranoia: look for ignored selectors with non-ignored implementations
    assert(!(ignoreSelector(sel)  &&  imp != (IMP)&_objc_ignored_method));

    // paranoia: never let uncached leak out
    assert(imp != _objc_msgSend_uncached_impcache);

    return imp;
}

接下来进入事件转发流程


/********************************************************************
*
* id _objc_msgForward(id self, SEL _cmd,...);
*
* _objc_msgForward and _objc_msgForward_stret are the externally-callable
*   functions returned by things like method_getImplementation().
* _objc_msgForward_impcache is the function pointer actually stored in
*   method caches.
*
********************************************************************/

    STATIC_ENTRY    __objc_msgForward_impcache
    // Method cache version

    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band condition register is NE for stret, EQ otherwise.

    MESSENGER_START
    nop
    MESSENGER_END_SLOW
    
    jne __objc_msgForward_stret
    jmp __objc_msgForward

    END_ENTRY   __objc_msgForward_impcache
    
    
    ENTRY   __objc_msgForward
    // Non-stret version

    movq    __objc_forward_handler(%rip), %r11
    jmp *%r11

    END_ENTRY   __objc_msgForward

调用__objc_msgForward之后调用了__objc_forward_handler方法

这个方法就是平时崩溃答应出来的文字的来源
// 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);
}

参考
Obj-C Optimization: The faster objc_msgSend
Source Browser源码

你可能感兴趣的:(Runtime-objc_msgSend源码)