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 searchlookUpImpOrForwardSure enough, I found the definition of the function

2 Verify the jump flow of the program

  • Main. m sets breakpoints and opensDebug->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_uncachedSee a bunch of registers after processing is carried outcallq

  • The program does enterlookUpImpOrForward 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 forwardingforward_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 toCacheLookup GETIMPAfter 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_handlerEnter C language functions
  • void *_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