iOS RunTime之三:消息发送

1,060 阅读12分钟

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 种,NORMALFPRETFP2RETSTRETobjc_msgSend 传入的参数是 NilTest NORMALobjc_msgSend_fpret 传入的参数是 NilTest FPRETobjc_msgSend_fp2ret 传入的参数是 NilTest FP2RETobjc_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