Introduction to the objc_msgSend function

In Objective-C, all “messages” in messaging are converted by the compiler to:

id objc_msgSend ( id self, SEL op, ... ) ;Copy the code
/** 
 * 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);
Copy the code

The comment roughly means that when the compiler encounters a method call, it translates the method call into one of the following functions: objc_msgSend, objc_msgSend_stret, objc_msgSendSuper, and objc_msgSendSuper_stret. Messages sent to the object’s parent class will use objc_msgSendSuper; Methods that have data structures as return values use objc_msgSendSuper_stret or objc_msgSend_stret; All other messages are sent using objc_msgSend;

Procedure for sending messages

The main steps of sending a message:

  • checkselectorWhether to ignore.
  • checktargetWhether it isnil. If it isnilDirectly,cleanupAnd thenreturn.This is why sending messages to nil in OC doesn’t crash;
  • Make sure it’s not fornilAfter sending the message, we start looking for the corresponding classIMPImplementation;

Find IMP process:

  • Start with the currentclasscacheLook it up in the list of methods.
  • If found, if found returns the correspondingIMPImplement and put the currentclassIn theselectorThe cache tocacheThe inside.
  • If you can’t find it in the method list of the class, go to the method list of the parent class, and keep finding itNSObjectClass so far.
  • When it does not, it enters dynamic method resolution and message forwarding.

Objc_msgSend source code parsing

In the case of x86_64, delete the rest of the code

Method / / send / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * id objc_msgSend (id self, SEL _cmd,...). ; * IMP objc_msgLookup(id self, SEL _cmd, ...) ; * * objc_msgLookup ABI: * IMP returnedin r11
 * Forwarding returned in Z flag
 * r10 reserved forour use but not used * ********************************************************************/ ENTRY _objc_msgSend UNWIND _objc_msgSend, NoFrame MESSENGER_START //1. Isa GetIsaFast NORMAL = self->isa //3 CALL IMP on success NilTestReturnZero NORMAL GetIsaSupport NORMAL // Cache miss go search the method lists LCacheMiss: // isa stillinR10 MESSENGER_END_SLOW //4. Search method 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 stillinr10 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_fixedupCopy the code

As you can see from the above assembly code, objc_msgSend can be handled with and without caching.

/////////////////////////////////////////////////////////////////////
//
// 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
Copy the code

NilTest is used to check if it is nil. There are four parameters passed in: NORMAL, FPRET, FP2RET, and STRET. Objc_msgSend is passed NilTest NORMAL; Objc_msgSend_fpret The argument passed in is NilTest FPRET; Objc_msgSend_fp2ret The argument passed in is NilTest FP2RET; Objc_msgSend_stret is passed as NilTest STRET; If the receiver of the detection method is nil, the system automatically cleans and returns.

/////////////////////////////////////////////////////////////////////
//
// 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 $The $1, %a1b
	PN
	jnz	LGetIsaSlow_f
	movq	$$0x00007ffffffffff8, %r10
	andq	(%a1), %r10
.else
	testb	$The $1, %a2b
	PN
	jnz	LGetIsaSlow_f
	movq	$$0x00007ffffffffff8, %r10
	andq	(%a2), %r10
.endif
LGetIsaDone:	
.endmacro
Copy the code

The GetIsaFast macro can quickly get the ISA pointer address of an object. r10 = obj->isa

No cache

__objc_msgSend_uncached () ¶ If not cached, the list of methods is searched and cached, and cached, and not cached, as shown in the objc_msgSend assembly code.

/******************************************************************** * * _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 for 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_uncachedCopy the code

Looks at the __objc_msgSend_uncached assembly code, which calls methodTable ELookup directly for the list of methods.

/////////////////////////////////////////////////////////////////////
//
// 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 forForwarding / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / assembly macro definition 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 ina2 .else movq %a2, %a1 movq %a3, %a2 .endif movq %r10, % a3 / / call the runtime __class_lookupMethodAndLoadCache3 call __class_lookupMethodAndLoadCache3 / / in the IMP is nowin %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
Copy the code

MethodTableLookup assembly macro definition is a piece of code, to find a useful information __class_lookupMethodAndLoadCache3, this function can not be found in the current assembly code. If you go to objC source for a global search, also can not be found. If it is a C function, in the bottom assembly, that is, if you need to call apple will add an underscore _ for it, so the above function delete an underscore, _class_lookupMethodAndLoadCache3;

/***********************************************************************
* _class_lookupMethodAndLoadCache.
* Method lookup fordispatchers 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*/);
}
Copy the code

LookUpImpOrForward is eventually called inside the function, which we’ll look at in detail.

/***********************************************************************
* 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(); // 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. The class implements 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(); // Class realizeClass(CLS); runtimeLock.unlockWrite(); runtimeLock.read(); } //2. If (initialize &&! cls->isInitialized()) { runtimeLock.unlockRead(); //-- class initialization _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 classImp = cache_getImp(CLS, sel); if (imp) goto done; // Try this class's method lists.
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if(meth) {//5. Add log_and_fill_cache(CLS, meth-> IMP, sel, inst, CLS); imp = meth->imp; gotodone; {unsigned attempts = unreasonableClassCount();for(Class curClass = cls->superclass; curClass ! = nil; curClass = curClass->superclass) { // Haltif there is a cycle in the superclass chain.
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list."); } Superclass cache. imp = cache_getImp(curClass, sel);if(imp) {// Check whether the cache is _objc_msgForward_impcacheif(imp ! = (IMP)_objc_msgForward_impcache) { // Found the methodin a superclass. Cache it inLog_and_fill_cache (CLS, IMP, SEL, inst, curClass); log_and_fill_cache(CLS, IMP, sel, curClass); gotodone;
                }
                else {
                    // Found a forward:: entry in a superclass.
                    // Stop searching, but don't cache yet; call method // resolver for this class first. break; Superclass method list. Method meth = getMethodNoSuper_nolock(curClass, sel); If (meth) {//(1) add method log_and_fill_cache(CLS, meth->imp, sel, inst, curClass); imp = meth->imp; goto done; // 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; } // No implementation found, and method resolver didn't 't help. // Use forwarding. imp = (IMP)_objc_msgForward_impcache; cache_fill(cls, sel, imp, inst); // IMP as _objc_msgForward_impcache is also added to the cachedone:
    runtimeLock.unlockRead();

    return imp;
}
Copy the code

Lock-free cache lookup

runtimeLock.assertUnlocked(); // Optimistic cache lookup skipped this conditional statementif (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }
Copy the code

In the absence of locking the cache lookup, improve the performance of the cache using because _class_lookupMethodAndLoadCache3 incoming cache = NO, so it will just skip the if the code execution, if the incoming is YES, The cache_getImp method is called to find the corresponding IMP in the cache.

//1. Whether the class is implementedif(! cls->isRealized()) { // Drop theread-lock and acquire the write-lock.
        // realizeClass() checks isRealized() again to prevent
        // a race whilethe lock is down. runtimeLock.unlockRead(); runtimeLock.write(); // Class realizeClass(CLS); runtimeLock.unlockWrite(); runtimeLock.read(); } //2. Whether the class is initializedif(initialize && ! cls->isInitialized()) { runtimeLock.unlockRead(); //-- class initialization _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 }Copy the code

In Objective-C runtime, during initialization, the class is initialized for the first time by executing the realizeClass method, allocating space for the class to read and write class_rw_t, and returning the correct class structure.

lock

runtimeLock.read();
Copy the code

Because methods are added dynamically at run time, locks are used to ensure thread-safety.

// Try this class's method lists. { Method meth = getMethodNoSuper_nolock(cls, sel); // add log_and_fill_cache(CLS, meth->imp, sel, inst, CLS) to the cache list; imp = meth->imp; goto done; }}Copy the code

Looking for the method’s implementation from the list of methods in the current class, call getMethodNoSuper_nolock to find the corresponding method’s structure pointer 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;
}
Copy the code

In the getMethodNoSuper_nolock method, you iterate over the methodList list once, from beginLists all the way to endLists. The search_method_list function is called during the traversal.

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;
}
Copy the code

In search_method_list function, to judge whether the current methodList ordered, if ordered, will call findMethodInSortedMethodList method, the realization of this method is a binary search, if not the order, It’s a fool’s walk through search.

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;
}
Copy the code

If an implementation of the method is found here, add it to the class cache.

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); }Copy the code

The analysis of this code is mentioned in the class structure of how cache_t caches SEL.

// Try superclass caches and method lists. {unsigned attempts = unreasonableClassCount();for(Class curClass = cls->superclass; curClass ! = nil; curClass = curClass->superclass) { // Haltif there is a cycle in the superclass chain.
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list."); } Superclass cache. imp = cache_getImp(curClass, sel);if(imp) {// Check whether the cache is _objc_msgForward_impcacheif(imp ! = (IMP)_objc_msgForward_impcache) { // Found the methodin a superclass. Cache it inLog_and_fill_cache (CLS, IMP, SEL, inst, curClass); log_and_fill_cache(CLS, IMP, sel, curClass); gotodone;
                }
                else {
                    // Found a forward:: entry in a superclass.
                    // Stop searching, but don't cache yet; call method // resolver for this class first. break; Superclass method list. Method meth = getMethodNoSuper_nolock(curClass, sel); If (meth) {//(1) add method log_and_fill_cache(CLS, meth->imp, sel, inst, curClass); imp = meth->imp; goto done; }}}Copy the code

If the method is not found in the current class object, the class object’s superclass pointer looks for the list of methods in the parent class. Similarly, the methods in the list of superclass methods are added to the current class cache instead of being cached in the superclass.

Have a cache

.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.The $1			// call or return imp

1:
	// loop
	cmpq	$The $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.The $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	$The $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.The $1			// call or return imp

3:
	// double wrap or miss
	jmp	LCacheMiss_f

.endmacro
Copy the code

In the CacheLookup function, bucket->sel is constantly compared to bucket->sel in the cache via _cmd. If bucket->sel < 1, jump to LCacheMiss_f flag to continue execution. If the program jumps to LCacheMiss, there is no cache in the cache. If the cache is not hit, you need to go to MethodTableLookup. If bucket->sel == _cmd finds sel in cache, execute IMP directly.