Summary of ios low-level articles
1 introduction
IOS method call, the underlying first lookup cache for a quick imp
For details about the process of searching cache for IMP, see ios-13. Method quick lookup process of lookup process
If a quick lookup of cache does not find a matching IMP, it goes to __objc_msgSend_uncached, and goes to the slow method list to query MethodTableLookup
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
MethodTableLookup // Query the list of methods
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
Copy the code
Methodtable – ookUp is a macro defined as follows:
.macro MethodTableLookup
// push frame
SignLR
stp fp.lr[sp# -16]!
mov fp.sp
// save parameter registers: x0.. x8, q0.. q7
sub sp.sp, # (10*8 + 8*16)
stp q0.q1[sp, # (0*16)]
stp q2.q3[sp, # (2*16)]
stp q4.q5[sp, # (4*16)]
stp q6.q7[sp, # (6*16)]
stp x0, x1, [sp, # (8*16+0*8)]
stp x2, x3, [sp, # (8*16+2*8)]
stp x4, x5, [sp, # (8*16+4*8)]
stp x6, x7, [sp, # (8*16+6*8)]
str x8, [sp, # (8*16+8*8)]
// 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
// restore registers and return
ldp q0.q1[sp, # (0*16)]
ldp q2.q3[sp, # (2*16)]
ldp q4.q5[sp, # (4*16)]
ldp q6.q7[sp, # (6*16)]
ldp x0, x1, [sp, # (8*16+0*8)]
ldp x2, x3, [sp, # (8*16+2*8)]
ldp x4, x5, [sp, # (8*16+4*8)]
ldp x6, x7, [sp, # (8*16+6*8)]
ldr x8, [sp, # (8*16+8*8)]
mov sp.fp
ldp fp.lr[sp].# 16
AuthenticateLR
.endmacro
Copy the code
From the macro definition of the MethodTableLookup, we know that a series of register operations are performed and _lookUpImpOrForward is returned. However, a global search for _lookUpImpOrForward fails to find its definition, and we assume that the underlying code goes from assembly to C source
Note: Jump from assembly macro to C function, function name missing an underscore prefix
Assembler macros are called from C functions, and their names are prefixed with an underscore
Global searchlookUpImpOrForward
Sure enough, I found the definition of the function
2 Verify the jump flow of the program
- Main. m sets breakpoints and opens
Debug->Debug Workflow->Always Show
Disassemble
- After I run the program, I go to the breakpoint,
Hold down the Control key + click Step into
, click step into multiple times until entering_objc_msgSend_uncached
See a bunch of registers after processing is carried outcallq
- The program does enter
lookUpImpOrForward at objc-runtime-new.mm:6099
Make the compiler ignore errors
3 lookUpImpOrForward Slow lookup logic
3.1 lookUpImpOrForward source
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
// Define message forwarding forward_IMP
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
runtimeLock.assertUnlocked();
// Multiple threads may update the cache and enter the fast lookup before entering the slow lookup
if (fastpath(behavior & LOOKUP_CACHE)) {
/* cache_getImp calls assembly: ``` STATIC_ENTRY _cache_getImp GetClassFromIsa_p16 p0 CacheLookup GETIMP, _cache_getImp LGetImpMiss: Mov P0, #0 ret END_ENTRY _cache_getImp '
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
// Add a lock to ensure thread-safe reading
runtimeLock.lock();
// Check whether the current class is a known class: Check whether the current class is an approved class, that is, a loaded class
checkIsKnownClass(cls);
// Check whether the class is implemented. If not, it needs to be implemented first. In this case, the purpose is to determine the parent class chain, method subsequent loop
if(slowpath(! cls->isRealized())) { cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock); }// Check whether the class is initialized. If not, initialize it first
if(slowpath((behavior & LOOKUP_INITIALIZE) && ! cls->isInitialized())) { cls = initializeAndLeaveLocked(cls, inst, runtimeLock); } runtimeLock.assertLocked(); curClass = cls;// Find the class cache
// unreasonableClassCount Maximum loop count
for (unsigned attempts = unreasonableClassCount();;) {
// List of current class methods (binary search algorithm), if found, return, cache method in the cache
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp;
goto done;
}
// Assign the current class = the parent of the current class and determine if the parent is nil
if (slowpath((curClass = curClass->superclass) == nil)) {
// No method implementation found, no method resolver, use forward
imp = forward_imp;
break;
}
// If attempts==0, it stops
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
Cache_getImp is the parent of the current class. If it is not found, lookUpImpOrForward is returned
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
Imp = forward_imp out of the loop if nil is not found in inheritance chain
break;
}
if (fastpath(imp)) {
// If the method is found in the parent class, store it in cache
gotodone; }}// Failed to find a method implementation, try a dynamic method resolution
if (slowpath(behavior & LOOKUP_RESOLVER)) {
// Control conditions for dynamic method resolution
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
// Store to cache
log_and_fill_cache(cls, imp, sel, inst, curClass);
/ / unlock
runtimeLock.unlock();
done_nolock:
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
Copy the code
3.2 lookUpImpOrForward Flow logic
- 1) Defined message forwarding
forward_imp
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
Copy the code
-
2) After entering the slow search, multithreading may update the cache, so it will enter the fast search before entering the slow search
-
3) cache_getImp will invoke the assembly to execute CacheLookup GETIMP, _cache_getImp
Pay attention to
CacheLookup GETIMP
After the search fails, go toLGetImpMiss
, the register is reset directly back, will not enter the slow search again
-
4) Enter the loop search process is
- List of current class methods
- The current class is assigned to the parent of the current class for iteration (determine whether the parent class is nil, if nil imp = forward_IMP message forward, exit the loop)
- If –attempts==0, the loop is stopped
- Find the fast superclass cache. If this method is found in the parent class of the inheritance chain, it breaks out of the loop and stores the method in the cache
-
5) Failed to find a method implementation, try a dynamic method resolution, and return the result of dynamic method resolution
-
6) If a method implementation is found, write the method to the cache log_and_fill_cache –> cache_fill
3.3 in the current class method list for imp (using binary search algorithm) : findMethodInSortedMethodList
GetMethodNoSuper_nolock - > cycle the methods called search_method_list_inline (* mlists, sel) -- - > findMethodInSortedMethodList
ALWAYS_INLINE 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;
/** binary search list incremental store */
for (count = list->count; count ! =0; count >>= 1) {
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)probe->name;
if (keyValue == probeValue) {
// There may be methods with the same name in the list
while (probe > first && keyValue == (uintptr_t)probe[- 1].name) {
// Remove the same class name method (method storage is first to store the class method, storage classification - according to the principle of first in, last out, classification method first, and we need to remove the class method first)
// If there are two categories, it depends on which one loads first
probe--;
}
return (method_t *)probe;
}
if (keyValue > probeValue) {
base = probe + 1; count--; }}return nil;
}
Copy the code
3.4 summarize
1) Object method lookup test
- They have, find and return;
- Find the parent class and return the parent class.
- No parent, no parent, find the parent, find the parent, return;
- Find the parent class down the inheritance chain, all the way to NSObject, and return if you find it;
- If none is found in the inheritance chain, crash
2) Class method lookup test
- They have, find and return;
- Find the parent class and return the parent class.
- No parent, no parent, find the parent, find the parent, return;
- Find the parent class down the inheritance chain, all the way to NSObject, and return if you find it;
- I go down the inheritance chain and I don’t find it all the way to NSObject, I look for the object method on NSObject, I find it and I return it;
- If you go down the inheritance chain and you don’t find the parent class until you get to NSObject, if you don’t find the object method on NSObject, it crashes
4 cache_getImp parsing
Imp = cache_getImp(CLS, SEL) imp = cache_getImp(CLS, SEL) imp = cache_getImp(CLS, SEL)
Cache_getImp (cache_getImp) cache_getImp (cache_getImp) cache_getImp (cache_getImp) Method lookup flow quick lookup flow)
STATIC_ENTRY _cache_getImp
GetClassFromIsa_p16 p0
CacheLookup GETIMP, _cache_getImp
LGetImpMiss:
mov p0.# 0
ret
END_ENTRY _cache_getImp
Copy the code
If the search fails, jump to LGetImpMiss, clear the register, and return directly. The slow search will not enter again
5 _objc_msgForward_impcache
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_msgForward_impcache
–>__objc_msgForward
–>_objc_forward_handler
Enter C language functionsvoid *_objc_forward_handler = (void*)objc_defaultForwardHandler
- Objc_defaultForwardHandler function implementation
// Default forward handler halts the process.
__attribute__((noreturn, cold)) void
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)) ? '+' : The '-',
object_getClassName(self), sel_getName(sel), self);
}
Copy the code
Dynamic method resolution, please pay attention to subsequent updates:Ios-15. objc_msgSend Dynamic method resolution and message forwarding