In the previous article, objc_msgSend called objc_msgSend_uncached after failing to find a method in the cache of a class. Here, we explore its flow.

objc_msgSend_uncached

        STATIC_ENTRY __objc_msgSend_uncached
	UNWIND __objc_msgSend_uncached, FrameWithNoSaves
	
	MethodTableLookup
	TailCallFunctionPointer x17

	END_ENTRY __objc_msgSend_uncached
Copy the code
macro MethodTableLookup

	// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
	// receiver and selector already in x0 and x1
	mov	x2, x16
	mov	x3, #3
	bl	_lookUpImpOrForward

	// IMP in x0
	mov	x17, x0

.endmacro
Copy the code
.macro TailCallFunctionPointer
	// $0 = function pointer value
	br	$0
.endmacro
Copy the code

As you can see from the assembly code, the METHODTable Ookup calls _lookUpImpOrForward to retrieve the IMP and stores the IMP in X0, which is the return value of this function. Then put imp in X17, jump X17 execution. The important thing is that _lookUpImpOrForward fetches imp, a function that searches globally, not in assembly but in C++.

_lookUpImpOrForward Indicates the slow lookup

  1. Preparation before the search
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass; 
    
    checkIsKnownClass(cls);

    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
Copy the code

Checks whether the current class exists and initializes a set of related classes and metaclasses. See ISA bitmap.

  1. Binary search – method

Binary search is a search algorithm for ordered lists, which is split in half at a time and compared with the value being looked for. For example, find 8 in the array [1,2,3,5,6,8,10]. The first time you half find 5, which is smaller than 8.

    for (unsigned attempts = unreasonableClassCount();;) {
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
            imp = cache_getImp(curClass, sel);
            if (imp) goto done_unlock;
            curClass = curClass->cache.preoptFallbackClass();
#endif
        } else {
            // curClass method list.
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                imp = meth->imp(false);
                goto done;
            }

            if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
                // No implementation found, and method resolver didn't help.
                // Use forwarding.
                imp = forward_imp;
                break;
            }
        }

        // Halt if there is a cycle in the superclass chain.
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }

        // Superclass cache.
        imp = cache_getImp(curClass, sel);
        if (slowpath(imp == forward_imp)) {
            // Found a forward:: entry in a superclass.
            // Stop searching, but don't cache yet; call method
            // resolver for this class first.
            break;
        }
        if (fastpath(imp)) {
            // Found the method in a superclass. Cache it in this class.
            goto done;
        }
    }

Copy the code

In an infinite loop, first look in the cache, and during this sequence of calls, the method may already be in the cache. If not, start looking up your own methodList. Enter getMethodNoSuper_nolock – > search_method_list_inline – > findMethodInSortedMethodList – > findMethodInSortedMethodList, So here’s apple’s binary search, let’s find SEL.

findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName) { ASSERT(list); auto first = list->begin(); auto base = first; decltype(first) 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)getName(probe); 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)getName((probe - 1))) { probe--; } return &*probe; } if (keyValue > probeValue) { base = probe + 1; count--; } } return nil; }Copy the code

For example list = [0, 1, 2, 3, 4, 5, 6, 7] first = 0, base = 0, count = 8

If the search key = 6 enters the loop for the first time Probe = base + count >> 1 = 0 + 4 = 4 key > probe base = probe + 1 = 5 count-- = 7 = 0 count >>= 1 = 3 probe = base + count >> 1 = 5 + 1 = 6 Found, return to ProbeCopy the code
Probe = base + count >> 1 = 0 + 4 = 4*** Enter the loop for the second time: count! = 0 count >>= 1 = 4 Probe = base + count >> 1 = 0 + 2 = 2*** = 0 count >>= 1 = 2 count >> 1 = 0 + 1 = 1*** Found, return to ProbeCopy the code
  1. Insert (sel, imp, receiver) to add the method to the cache, and the next time you can directly find the method quickly.

  2. Cache_getImp (curClass, sel); imp = cache_getImp(curClass, sel); imp = cache_getImp(curClass, sel); If not, look it up in the parent class, and so on.

If (slowPath ((curClass = curClass->getSuperclass()) == nil)) {slowPath = curClass->getSuperclass() == nil) implementation found, and method resolver didn't help. // Use forwarding. imp = forward_imp; break; } // Superclass cache. imp = cache_getImp(curClass, sel);Copy the code
	STATIC_ENTRY _cache_getImp

	GetClassFromIsa_p16 p0, 0
	CacheLookup GETIMP, _cache_getImp, LGetImpMissDynamic, LGetImpMissConstant

LGetImpMissDynamic:
	mov	p0, #0
	ret

LGetImpMissConstant:
	mov	p0, p2
	ret

	END_ENTRY _cache_getImp
Copy the code
  1. If there are no methods in NSObject and the parent class is nil, it is calledimp = forward_imp, that is,_objc_msgForward_impcache(assignment at step 1), out of the loop, an error is reported. unrecognized selector sent....
        STATIC_ENTRY __objc_msgForward_impcache

	// No stret specialization.
	b	__objc_msgForward

	END_ENTRY __objc_msgForward_impcache

	
	ENTRY __objc_msgForward

	adrp	x17, __objc_forward_handler@PAGE
	ldr	p17, [x17, __objc_forward_handler@PAGEOFF]
	TailCallFunctionPointer x17
	
	END_ENTRY __objc_msgForward
        
Copy the code
objc_defaultForwardHandler(id self, SEL sel)
{
    _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
                "(no message forward handler is installed)", 
                class_isMetaClass(object_getClass(self)) ? '+' : '-', 
                object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
Copy the code