iOS RunTime之三:消息发送

objc_msgSend 函数简介

在 Objective-C 中,所有的消息传递中的“消息”都会被编译器转化为:

id objc_msgSend ( id self, SEL op, ... );
/** 
 * 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_msgSendobjc_msgSend_stretobjc_msgSendSuperobjc_msgSendSuper_stret
发送给对象的父类的消息会使用 objc_msgSendSuper
有数据结构作为返回值的方法会使用 objc_msgSendSuper_stretobjc_msgSend_stret
其它的消息都是使用 objc_msgSend 发送的;

消息发送步骤

image.png

消息发送的主要步骤:

  • 检查 selector 是否需要忽略。
  • 检查 target 是否为 nil。如果为 nil,直接 cleanup,然后 return这一点就是为何在OC中给nil发送消息不会崩溃的原因;
  • 确定不是给 nil 发消息之后,就开始查找这个类对应的 IMP 实现;
image

查找 IMP 的过程:

  • 先从当前 classcache 方法列表里去查找。
  • 如果找到了,如果找到了就返回对应的 IMP 实现,并把当前的 class 中的 selector 缓存到 cache 里面。
  • 如果类的方法列表中找不到就到父类的方法列表中查找,一直找到 NSObject 类为止。
  • 最后再找不到,就会进入动态方法解析和消息转发的机制。

objc_msgSend 源码解析

以 x86_64 为例,删除其他的代码

//方法发送
/********************************************************************
 *
 * id objc_msgSend(id self, SEL _cmd,...);
 * IMP objc_msgLookup(id self, SEL _cmd, ...);
 *
 * objc_msgLookup ABI:
 * IMP returned in r11
 * Forwarding returned in Z flag
 * r10 reserved for our use but not used
 *
 ********************************************************************/
    
    ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame
    MESSENGER_START

    //1.判断空处理
    NilTest NORMAL

    //2.获取isa
    GetIsaFast NORMAL       // r10 = self->isa
    //3.缓存中查找
    CacheLookup NORMAL, CALL    // calls IMP on success

    NilTestReturnZero NORMAL

    GetIsaSupport NORMAL

// cache miss: go search the method lists
LCacheMiss:
    // isa still in r10
    MESSENGER_END_SLOW
    //4.查找方法
    jmp __objc_msgSend_uncached

    END_ENTRY _objc_msgSend

    
    ENTRY _objc_msgLookup

    NilTest NORMAL

    GetIsaFast NORMAL       // r10 = self->isa
    CacheLookup NORMAL, LOOKUP  // returns IMP on success

    NilTestReturnIMP NORMAL

    GetIsaSupport NORMAL

// cache miss: go search the method lists
LCacheMiss:
    // isa still in r10
    jmp __objc_msgLookup_uncached

    END_ENTRY _objc_msgLookup

    
    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

从上面汇编代码中看出,objc_msgSend,会分为有缓存和无缓存两种情况的处理。

/////////////////////////////////////////////////////////////////////
//
// 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.
//
// NilTestReturnZero 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.
//
// NilTestReturnIMP 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 an IMP in r11 that returns zero.
//
/////////////////////////////////////////////////////////////////////

.macro ZeroReturn
    xorl    %eax, %eax
    xorl    %edx, %edx
    xorps   %xmm0, %xmm0
    xorps   %xmm1, %xmm1
.endmacro

.macro ZeroReturnFPRET
    fldz
    ZeroReturn
.endmacro

.macro ZeroReturnFP2RET
    fldz
    fldz
    ZeroReturn
.endmacro

.macro ZeroReturnSTRET
    // rax gets the struct-return address as passed in rdi
    movq    %rdi, %rax
.endmacro

    STATIC_ENTRY __objc_msgNil
    ZeroReturn
    ret
    END_ENTRY __objc_msgNil

    STATIC_ENTRY __objc_msgNil_fpret
    ZeroReturnFPRET
    ret
    END_ENTRY __objc_msgNil_fpret

    STATIC_ENTRY __objc_msgNil_fp2ret
    ZeroReturnFP2RET
    ret
    END_ENTRY __objc_msgNil_fp2ret

    STATIC_ENTRY __objc_msgNil_stret
    ZeroReturnSTRET
    ret
    END_ENTRY __objc_msgNil_stret


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


.macro NilTestReturnZero
    .align 3
LNilTestSlow:
    
.if $0 == NORMAL
    ZeroReturn
.elseif $0 == FPRET
    ZeroReturnFPRET
.elseif $0 == FP2RET
    ZeroReturnFP2RET
.elseif $0 == STRET
    ZeroReturnSTRET
.else
.abort oops
.endif
    MESSENGER_END_NIL
    ret 
.endmacro


.macro NilTestReturnIMP
    .align 3
LNilTestSlow:
    
.if $0 == NORMAL
    leaq    __objc_msgNil(%rip), %r11
.elseif $0 == FPRET
    leaq    __objc_msgNil_fpret(%rip), %r11
.elseif $0 == FP2RET
    leaq    __objc_msgNil_fp2ret(%rip), %r11
.elseif $0 == STRET
    leaq    __objc_msgNil_stret(%rip), %r11
.else
.abort oops
.endif
    ret
.endmacro

NilTest 是用来检测是否为 nil 的。传入参数有 4 种,NORMALFPRETFP2RETSTRET
objc_msgSend 传入的参数是 NilTest NORMAL
objc_msgSend_fpret 传入的参数是 NilTest FPRET
objc_msgSend_fp2ret 传入的参数是 NilTest FP2RET
objc_msgSend_stret 传入的参数是 NilTest STRET
如果检测方法的接受者是 nil,那么系统会自动 clean 并且 return

/////////////////////////////////////////////////////////////////////
//
// GetIsaFast return-type
// GetIsaSupport return-type
//
// Sets r10 = obj->isa. Consults the tagged isa table if necessary.
//
// Takes:   $0 = NORMAL or FPRET or FP2RET or STRET
//      a1 or a2 (STRET) = receiver
//
// On exit:     r10 = receiver->isa
//      r11 is clobbered
//
/////////////////////////////////////////////////////////////////////

.macro GetIsaFast
.if $0 != STRET
    testb   $$1, %a1b
    PN
    jnz LGetIsaSlow_f
    movq    $$0x00007ffffffffff8, %r10
    andq    (%a1), %r10
.else
    testb   $$1, %a2b
    PN
    jnz LGetIsaSlow_f
    movq    $$0x00007ffffffffff8, %r10
    andq    (%a2), %r10
.endif
LGetIsaDone:    
.endmacro

GetIsaFast 宏可以快速地获取到对象的 isa 指针地址。r10 = obj->isa

无缓存

通过 objc_msgSend 汇编代码中可以看出,如果没有命中缓存,会搜索方法列表,程序跳到 __objc_msgSend_uncached,就说明 cache 中无缓存,未命中缓存。

/********************************************************************
 *
 * _objc_msgSend_uncached
 * _objc_msgSend_stret_uncached
 * _objc_msgLookup_uncached
 * _objc_msgLookup_stret_uncached
 *
 * The uncached method lookup.
 *
 ********************************************************************/

    STATIC_ENTRY __objc_msgSend_uncached
    UNWIND __objc_msgSend_uncached, FrameWithNoSaves
    
    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band r10 is the searched class

    // r10 is already the class to search
    MethodTableLookup NORMAL    // r11 = IMP
    jmp *%r11           // goto *imp

    END_ENTRY __objc_msgSend_uncached

    
    STATIC_ENTRY __objc_msgSend_stret_uncached
    UNWIND __objc_msgSend_stret_uncached, FrameWithNoSaves
    
    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band r10 is the searched class

    // r10 is already the class to search
    MethodTableLookup STRET     // r11 = IMP
    jmp *%r11           // goto *imp

    END_ENTRY __objc_msgSend_stret_uncached

    
    STATIC_ENTRY __objc_msgLookup_uncached
    UNWIND __objc_msgLookup_uncached, FrameWithNoSaves
    
    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band r10 is the searched class

    // r10 is already the class to search
        // 方法列表中查找
    MethodTableLookup NORMAL    // r11 = IMP
    ret

    END_ENTRY __objc_msgLookup_uncached

    
    STATIC_ENTRY __objc_msgLookup_stret_uncached
    UNWIND __objc_msgLookup_stret_uncached, FrameWithNoSaves
    
    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band r10 is the searched class

    // r10 is already the class to search
    MethodTableLookup STRET     // r11 = IMP
    ret

    END_ENTRY __objc_msgLookup_stret_uncached

查看 __objc_msgSend_uncached 汇编代码,会直接调用 MethodTableLookup 中查找方法列表。

/////////////////////////////////////////////////////////////////////
//
// MethodTableLookup NORMAL|STRET
//
// Takes:   a1 or a2 (STRET) = receiver
//      a2 or a3 (STRET) = selector to search for
//      r10 = class to search
//
// On exit: imp in %r11, eq/ne set for forwarding
//
/////////////////////////////////////////////////////////////////////
//汇编宏定义 MethodTableLookup
.macro MethodTableLookup

    push    %rbp
    mov %rsp, %rbp
    
    sub $$0x80+8, %rsp      // +8 for alignment

    movdqa  %xmm0, -0x80(%rbp)
    push    %rax            // might be xmm parameter count
    movdqa  %xmm1, -0x70(%rbp)
    push    %a1
    movdqa  %xmm2, -0x60(%rbp)
    push    %a2
    movdqa  %xmm3, -0x50(%rbp)
    push    %a3
    movdqa  %xmm4, -0x40(%rbp)
    push    %a4
    movdqa  %xmm5, -0x30(%rbp)
    push    %a5
    movdqa  %xmm6, -0x20(%rbp)
    push    %a6
    movdqa  %xmm7, -0x10(%rbp)

    // _class_lookupMethodAndLoadCache3(receiver, selector, class)

.if $0 == NORMAL
    // receiver already in a1
    // selector already in a2
.else
    movq    %a2, %a1
    movq    %a3, %a2
.endif
    movq    %r10, %a3
    //调用runtime中的__class_lookupMethodAndLoadCache3
    call    __class_lookupMethodAndLoadCache3

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

    movdqa  -0x80(%rbp), %xmm0
    pop %a6
    movdqa  -0x70(%rbp), %xmm1
    pop %a5
    movdqa  -0x60(%rbp), %xmm2
    pop %a4
    movdqa  -0x50(%rbp), %xmm3
    pop %a3
    movdqa  -0x40(%rbp), %xmm4
    pop %a2
    movdqa  -0x30(%rbp), %xmm5
    pop %a1
    movdqa  -0x20(%rbp), %xmm6
    pop %rax
    movdqa  -0x10(%rbp), %xmm7

.if $0 == NORMAL
    cmp %r11, %r11      // set eq for nonstret forwarding
.else
    test    %r11, %r11      // set ne for stret forwarding
.endif
    
    leave

.endmacro

MethodTableLookup 是汇编宏定义的一段代码,从中发现一个有用的信息 __class_lookupMethodAndLoadCache3,这个函数在当前的汇编代码里面是找不到实现的。如果去 objc 源码进行全局搜索,也搜不到。如果是一个 C 函数,在底层汇编里面如果需要调用的话,苹果会为其加一个下划线 _,因此上面的的函数删去一个下划线,_class_lookupMethodAndLoadCache3

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

而这个函数里面最终是调用了 lookUpImpOrForward 函数,下面具体分析一下这个函数;

/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup. 
* initialize==NO tries to avoid +initialize (but sometimes fails)
* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use initialize==YES and cache==YES.
* inst is an instance of cls or a subclass thereof, or nil if none is known. 
*   If cls is an un-initialized metaclass then a non-nil inst is faster.
* May return _objc_msgForward_impcache. IMPs destined for external use 
*   must be converted to _objc_msgForward or _objc_msgForward_stret.
*   If you don't want forwarding at all, use lookUpImpOrNil() instead.
**********************************************************************/
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    //cache传入值为NO跳过此条件语句
    // Optimistic cache lookup
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

    // runtimeLock is held during isRealized and isInitialized checking
    // to prevent races against concurrent realization.

    // runtimeLock is held during method search to make
    // method-lookup + cache-fill atomic with respect to method addition.
    // Otherwise, a category could be added but ignored indefinitely because
    // the cache was re-filled with the old value after the cache flush on
    // behalf of the category.

    runtimeLock.read();

    //1.类是否实现了
    if (!cls->isRealized()) {
        // Drop the read-lock and acquire the write-lock.
        // realizeClass() checks isRealized() again to prevent
        // a race while the lock is down.
        runtimeLock.unlockRead();
        runtimeLock.write();

        //类实现
        realizeClass(cls);

        runtimeLock.unlockWrite();
        runtimeLock.read();
    }

    //2.类是否初始化了
    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlockRead();
        //---类初始化
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.read();
        // 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:    
    runtimeLock.assertReading();

    // Try this class's cache.
    //3.查找缓存列表
    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    //4.当前类方法列表中查找
    // Try this class's method lists.
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            //5.添加到缓存列表中
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

    //6.循环父类方法列表中查找
    // 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.");
            }
            
            //(1)查找父类缓存列表
            // Superclass cache.
            imp = cache_getImp(curClass, sel);
            if (imp) {
                //需要判断缓存是否_objc_msgForward_impcache
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // Found the method in a superclass. Cache it in this class.
                    //(2)把父类缓存中的方法添加到当前类缓存中
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    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;
                }
            }
            
            //(3)查找父类方法列表
            // Superclass method list.
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                //(4)把方法列表中的方法添加到当前类缓存中
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }

    //6.IMP没有找到,尝试方法解析一次
    // No implementation found. Try method resolver once.

    if (resolver  &&  !triedResolver) {
        runtimeLock.unlockRead();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.read();
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;
    }
    //7.IMP仍然没有找到,并且解析失败,则使用消息转发
    // No implementation found, and method resolver didn't help. 
    // Use forwarding.

    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);        //当IMP为_objc_msgForward_impcache也添加到缓存中去了

 done:
    runtimeLock.unlockRead();

    return imp;
}

无锁的缓存查找

    runtimeLock.assertUnlocked();

    //cache传入值为NO跳过此条件语句
    // Optimistic cache lookup
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

在没有加锁的时候对缓存进行查找,提高缓存使用的性能,因为 _class_lookupMethodAndLoadCache3 传入的 cache = NO,所以这里会直接跳过 if 中代码的执行,如果传入的是 YES,那么就会调用 cache_getImp 方法去找到缓存里面对应的 IMP

    //1.类是否实现了
    if (!cls->isRealized()) {
        // Drop the read-lock and acquire the write-lock.
        // realizeClass() checks isRealized() again to prevent
        // a race while the lock is down.
        runtimeLock.unlockRead();
        runtimeLock.write();

        //类实现
        realizeClass(cls);

        runtimeLock.unlockWrite();
        runtimeLock.read();
    }

    //2.类是否初始化了
    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlockRead();
        //---类初始化
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.read();
        // 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
    }

Objective-C 运行时,初始化的过程中会对其中的类进行第一次初始化也就是执行 realizeClass 方法,为类分配可读写结构体 class_rw_t 的空间,并返回正确的类结构体。

加锁

runtimeLock.read();

因为在运行时中会动态的添加方法,为了保证线程安全,所以要加锁。

    //4.当前类方法列表中查找
    // Try this class's method lists.
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            //5.添加到缓存列表中
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

从当前类的方法列表中寻找方法的实现,调用 getMethodNoSuper_nolock 方法查找对应的方法的结构体指针 method_t

static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();

    assert(cls->isRealized());
    // fixme nil cls? 
    // fixme nil sel?

    for (auto mlists = cls->data()->methods.beginLists(), 
              end = cls->data()->methods.endLists(); 
         mlists != end;
         ++mlists)
    {
        method_t *m = search_method_list(*mlists, sel);
        if (m) return m;
    }

    return nil;
}

getMethodNoSuper_nolock 方法中,会遍历一次 methodList 链表,从 beginLists 一直遍历到 endLists。遍历过程中会调用 search_method_list 函数。

static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
    int methodListIsFixedUp = mlist->isFixedUp();
    int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
    
    if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
        return findMethodInSortedMethodList(sel, mlist);
    } else {
        // Linear search of unsorted method list
        for (auto& meth : *mlist) {
            if (meth.name == sel) return &meth;
        }
    }

#if DEBUG
    // sanity-check negative results
    if (mlist->isFixedUp()) {
        for (auto& meth : *mlist) {
            if (meth.name == sel) {
                _objc_fatal("linear search worked when binary search did not");
            }
        }
    }
#endif

    return nil;
}

search_method_list 函数中,会去判断当前 methodList 是否有序,如果有序,会调用 findMethodInSortedMethodList 方法,这个方法里面的实现是一个二分搜索,如果非有序,就傻瓜式的遍历搜索。

static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
    assert(list);

    const method_t * const first = &list->first;
    const method_t *base = first;
    const method_t *probe;
    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    
    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);
        
        uintptr_t probeValue = (uintptr_t)probe->name;
        
        if (keyValue == probeValue) {
            // `probe` is a match.
            // Rewind looking for the *first* occurrence of this value.
            // This is required for correct category overrides.
            while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
                probe--;
            }
            return (method_t *)probe;
        }
        
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    
    return nil;
}

如果在这里找到了方法的实现,将它加入类的缓存中。

static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
    if (objcMsgLogEnabled) {
        bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                      cls->nameForLogging(),
                                      implementer->nameForLogging(), 
                                      sel);
        if (!cacheIt) return;
    }
#endif
    cache_fill (cls, sel, imp, receiver);
}

void cache_fill(Class cls, SEL sel, IMP imp, id receiver)
{
#if !DEBUG_TASK_THREADS
    mutex_locker_t lock(cacheUpdateLock);
    cache_fill_nolock(cls, sel, imp, receiver);
#else
    _collecting_in_critical();
    return;
#endif
}

static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver)
{
    cacheUpdateLock.assertLocked();

    // Never cache before +initialize is done
    if (!cls->isInitialized()) return;

    // Make sure the entry wasn't added to the cache by some other thread 
    // before we grabbed the cacheUpdateLock.
    if (cache_getImp(cls, sel)) return;

    cache_t *cache = getCache(cls);
    cache_key_t key = getKey(sel);

    // Use the cache as-is if it is less than 3/4 full
    mask_t newOccupied = cache->occupied() + 1;
    mask_t capacity = cache->capacity();
    if (cache->isConstantEmptyCache()) {
        // Cache is read-only. Replace it.
        cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);
    }
    else if (newOccupied <= capacity / 4 * 3) {
        // Cache is less than 3/4 full. Use it as-is.
    }
    else {
        // Cache is too full. Expand it.
        cache->expand();
    }

    // Scan for the first unused slot and insert there.
    // There is guaranteed to be an empty slot because the 
    // minimum size is 4 and we resized at 3/4 full.
    bucket_t *bucket = cache->find(key, receiver);
    if (bucket->key() == 0) cache->incrementOccupied();
    bucket->set(key, imp);
}

这段代码的分析在 类结构中 cache_t 如何缓存 sel 提到过。

  //6.循环父类方法列表中查找
    // 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.");
            }
            
            //(1)查找父类缓存列表
            // Superclass cache.
            imp = cache_getImp(curClass, sel);
            if (imp) {
                //需要判断缓存是否_objc_msgForward_impcache
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // Found the method in a superclass. Cache it in this class.
                    //(2)把父类缓存中的方法添加到当前类缓存中
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    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;
                }
            }
            
            //(3)查找父类方法列表
            // Superclass method list.
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                //(4)把方法列表中的方法添加到当前类缓存中
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }

如果在当前 class 对象里面没有找到该方法,那么会通过 class 对象的 superclass 指针查找父类的 class 的方法列表。同理,找到后把父类方法列表中的方法添加到当前类缓存中,而不是缓存到父类中。

有缓存

.macro  CacheLookup
.if $0 != STRET
    movq    %a2, %r11       // r11 = _cmd
.else
    movq    %a3, %r11       // r11 = _cmd
.endif
    andl    24(%r10), %r11d     // r11 = _cmd & class->cache.mask
    shlq    $$4, %r11       // r11 = offset = (_cmd & mask)<<4
    addq    16(%r10), %r11      // r11 = class->cache.buckets + offset

.if $0 != STRET
    cmpq    (%r11), %a2     // if (bucket->sel != _cmd)
.else
    cmpq    (%r11), %a3     // if (bucket->sel != _cmd)
.endif
    jne     1f          //     scan more
    // CacheHit must always be preceded by a not-taken `jne` instruction
    CacheHit $0, $1         // call or return imp

1:
    // loop
    cmpq    $$1, (%r11)
    jbe 3f          // if (bucket->sel <= 1) wrap or miss

    addq    $$16, %r11      // bucket++
2:  
.if $0 != STRET
    cmpq    (%r11), %a2     // if (bucket->sel != _cmd)
.else
    cmpq    (%r11), %a3     // if (bucket->sel != _cmd)
.endif
    jne     1b          //     scan more
    // CacheHit must always be preceded by a not-taken `jne` instruction
    CacheHit $0, $1         // call or return imp

3:
    // wrap or miss
    jb  LCacheMiss_f        // if (bucket->sel < 1) cache miss
    // wrap
    movq    8(%r11), %r11       // bucket->imp is really first bucket
    jmp     2f

    // Clone scanning loop to miss instead of hang when cache is corrupt.
    // The slow path may detect any corruption and halt later.

1:
    // loop
    cmpq    $$1, (%r11)
    jbe 3f          // if (bucket->sel <= 1) wrap or miss

    addq    $$16, %r11      // bucket++
2:  
.if $0 != STRET
    cmpq    (%r11), %a2     // if (bucket->sel != _cmd)
.else
    cmpq    (%r11), %a3     // if (bucket->sel != _cmd)
.endif
    jne     1b          //     scan more
    // CacheHit must always be preceded by a not-taken `jne` instruction
    CacheHit $0, $1         // call or return imp

3:
    // double wrap or miss
    jmp LCacheMiss_f

.endmacro

这段汇编代码大概的意思是在这个 CacheLookup 函数中,不断的通过 _cmdcache 中的 bucket->sel 进行比较。如果 bucket->sel < 1,则跳转到 LCacheMiss_f 标记去继续执行。程序跳到 LCacheMiss,就说明 cache 中无缓存,未命中缓存,则要去 MethodTableLookup 查找。如果 bucket->sel == _cmd 即在 cache 中找到了相应的 SEL,则直接执行该 IMP

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的...
    西木阅读 30,539评论 33 466
  • 本文转载自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex阅读 744评论 0 1
  • 文中的实验代码我放在了这个项目中。 以下内容是我通过整理[这篇博客] (http://yulingtianxia....
    茗涙阅读 909评论 0 6
  • 2010年的暑假开学,佳人作为大一新生,认识了大她两岁的沈二。 那天和舍友在食堂外面看见了街舞社纳新的海报,佳人死...
    不想栽树阅读 483评论 0 5