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
- 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.
- 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
-
Insert (sel, imp, receiver) to add the method to the cache, and the next time you can directly find the method quickly.
-
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
- If there are no methods in NSObject and the parent class is nil, it is called
imp = 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