The introduction of

Objective-c programs have three ways to interact with the runtime system

1. Use objective-C source code

2. Through NSObject in the Foundation framework

3. By calling the API interface provided by the runtime system

We’d love to see what happens in the main.m file when clang compiles, Use xcrun-sdk iphoneos clang-arch arm64-rewrite-objc main.m -o main. CPP

You can see here whereObjective-CThe essence of the method inobjc_msgSend“, and the default takes two parameters, the first is the method recipient, the second is the method number, so how to passselFind the implementation of the methodIMP?

You can see that from hereObjective-CThe essence of an object is a structure whose template isobjc_object, one for each objectisa.

Method call

Method invocation is divided into three types: instance invocation method, class invocation method, and parent invocation method

  • The instanceA method is called

objc_msgSend(p, @selector(run));

  • classA method is called

1. Use Pointers

id class = [Person class];
void *pointClass = &class;
[(__bridge id)pointClass walk];
Copy the code

2. Send messages

objc_msgSend(objc_getClass("Person"), @selector(walk));
Copy the code
  • The parent classCalling instance methods
Student *s = [Student new];
        
struct objc_super mySuper;
mySuper.receiver = s;
mySuper.super_class = class_getSuperclass([s class]);

objc_msgSendSuper(&mySuper, @selector(run));
Copy the code
  • The parent classCalling class methods
struct objc_super myClassSuper;
myClassSuper.receiver = [s class];
myClassSuper.super_class = class_getSuperclass(object_getClass([Student class]));
        
objc_msgSendSuper(&myClassSuper, @selector(walk));
Copy the code

Instance methods are stored in classes, and class methods are stored in metaclasses as well as instance methods. What about the flow of sending messages?

Method lookup

Two ways of objc_msgSend

  • The quick way: by findingThe cacheIn the assembly
  • The slow way:C,C++Find together with the assembly

cacheContained in theselandIMPThe cache will look for a file fromselandIMPHash table, if you can find it directly from it, it will return it, very fast, but if it’s not in the cache, it will go into a slower mode, and when it finds it, it will store it in this hash table.

objc_msgSend

Look for objc_msgSend directly in the ARM-64 assembler

1.LNilOrTaggedThis step determines whether the current object isTagged-PointObject, which I won’t explain here 2.LGetIsaDonerightIsaProceed to process 3.CacheLookup NORMALFind in cacheIMP

So here you can see that there are three different scenarios that we can talk about in cache up, because if you’re looking in cache, you either find it or you don’t find it

CacheLookup

CacheHit

Hit in cache

Call IMP directly

CheckMiss

Not found in cacheBecause the parameter isNORMALHave managed__objc_msgSend_uncached

MethodTableLookupAfter looking it up in the method list, call the function pointer directly

Here, I will go straight back to C from assembly. The above is a general process analysis, and then I will make a detailed analysis of the fast search process and slow search process.

CacheLookupQuick Lookup process

.macro CacheLookup // // Restart protocol: // // As soon as we're past the LLookupStart$1 label we may have loaded // an invalid cache pointer or mask. // // When task_restartable_ranges_synchronize() is called, // (or when a signal hits us) before we're past LLookupEnd$1, // then our PC will be reset to LLookupRecover$1 which forcefully // jumps to the cache-miss codepath which have the following // requirements: // // GETIMP: // The cache-miss is just returning NULL (setting x0 to 0) // // NORMAL and LOOKUP: // - x0 contains the receiver // - x1 contains the selector // - x16 contains the isa // - other registers are set as per calling conventions // LLookupStart$1: // p1 = SEL, P16 = isa /* find definition #define CACHE (2 * __SIZEOF_POINTER__) P1 = SEL p16 = isa isa offset 16 bytes exactly is the CACHE to store CACHE in P11 LDR p11, [x16, # CACHE] / / p11 = mask | buckets / / # 64: if CACHE_MASK_STORAGE = = CACHE_MASK_STORAGE_HIGH_16 / * p11 storage CACHE high 16 position 0, P10 */ and p10, p11 # 0x0000ffffFFFFFFFF // p10 = buckets /* P11 storage cache 48 bits right to obtain mask P1 storage SEL _cmd & mask storage SEL imp subscript index (search subscript) results stored in P12 #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4 and p10, p11, #~0xf // p10 = buckets and p11, p11, #0xf // p11 = maskShift mov p12, #0xffff lsr p11, p12, p11 // p11 = mask = 0xffff >> p11 and p12, p1, P11 // x12 = _cmd & mask #else #error Unsupported cache mask storage for ARM64 LSL #(1+PTRSHIFT) */ add P12, P10, P12, LSL #(1+PTRSHIFT) // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT)) /* P12 stores buckets p17 imp P9 SEL */ LDP P17, p9, [x12] {imp, sel} = *bucket // compare sel with p1 (CMD) 1: CMP p9, p1 // if (bucket->sel! CacheHit $0 // call or return imp 2: // not hit: p12 = not-hit bucket // CheckMiss $0 // miss if bucket->sel == 0 cmp p12, p10 // wrap if bucket == buckets b.eq 3f ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket b 1b // loop 3: // wrap: p12 = first bucket, W11 = mask #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 /* It is equivalent to mask*16 and the first address of buckets is offset. */ add p12, p12, p11, LSR #(48 - (1+PTRSHIFT)) // p12 = buckets + (mask << 1+PTRSHIFT) #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4 add p12, p12, p11, LSL #(1+PTRSHIFT) // p12 = buckets + (mask << 1+PTRSHIFT) #else #error Unsupported cache mask storage for ARM64. #endif // Clone scanning loop to miss instead of hang when cache is corrupt. // The slow path may detect any corruption and /* P17 imp p9 SEL */ LDP p17, p9, [x12] // {imp, sel} = *bucket 1: cmp p9, p1 // if (bucket->sel ! = _cmd) b.ne 2f // scan more CacheHit $0 // call or return imp 2: // not hit: P12 = not hit bucket CheckMiss $0 // miss if bucket->sel == 0 /* CMP p12 = not hit bucket CheckMiss $0 // miss if bucket->sel == 0 /* CMP p12 = not hit bucket CheckMiss $0 // miss if bucket->sel == 0 P17, p9, [x12, # -bucket_size]! // {imp, sel} = *--bucket b 1b // loop LLookupEnd$1: LLookupRecover$1: 3: // double wrap JumpMiss $0 .endmacroCopy the code

The overall process is roughly divided into the following steps

#define CACHE (2 * SIZEOF_POINTER) p1 = SEL p16 = isa isa offset 16 bytes is exactly the CACHE to store CACHE p11 (mask high 16 bits +) Buckets 48 places lower)

The second step is to extract the mask and buckets from P11, the cache

  • Due to thecacheThe structure ofmaskAccount forHigh 16.bucketsinLower 48, so adopt and0x0000ffffffffffffCarry out and operation to clear the high 16 bits, directly getbucketsdepositp10
  • The same will becacheMoves to the right48Get the height directly16themaskThat will bep1In theselwithmaskAnd we getsel-impThe subscriptIndex (search subscript)The results inp12And why do we do that, because we use the same algorithm for storage.

The third step is to find the corresponding bucket based on the search index obtained from finding buckets

– If PTRSHIFT is 3, 1+PTRSHIFT is 4. When you search for p12, the subscript moves 4 bits to the left, that is, the subscript *16 bytes corresponds to p12. If a bucket occupies 16 bytes, you can obtain the corresponding bucket by moving the starting address of buckets

Step 4 According to the obtained bucket, remove imp and store P17, and remove SEL and store P9

Step 5 enters the first level of recursive loop search

  • Compare sel in the found bucket to _cmd in objc_msgSend

  • If they are equal, they hit, go to CacheHit, and return IMP

  • If it is not, we will discuss it again in two cases

  • If not, CheckMiss is used, and the parameter is NORMAL, __objc_msgSend_uncached is used

  1. If bucket is the first bucket of buckets, then mask in P11 will be moved 44 bits to the right, which is equivalent to mask*16, and the first bucket of buckets will be offset to get the last bucket. At this point, the recursive search of the second layer begins.

  2. If the bucket is not buckets’ first element, the search continues to the first level in a recursive loop.

Step 6 enter the second level of recursive loop search

  • Compare sel in the found bucket to _cmd in objc_msgSend

  • If equal, CacheHit is hit and IMP is returned

  • If not, the bucket goes to JumpMiss and __objc_msgSend_uncached (NORMAL) until bucket is the first element of buckets

If not, skip to __objc_msgSend_uncached

MethodTableLookup calls the function pointer when it is found in the method list

This is where we jump from assembly to C, C++, where we are familiar

lookUpImpOrForwardSlow search process

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) { const IMP forward_imp = (IMP)_objc_msgForward_impcache; IMP imp = nil; Class curClass; runtimeLock.assertUnlocked(); // Optimistic cache lookup /* Fast lookup. */ if (fastPath (behavior & LOOKUP_CACHE)) {imp = cache_getImp(CLS, sel); if (imp) goto done_nolock; } // 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 */ runtimelock.lock (); // We don't want people to be able to craft a binary blob that looks like // a class but really isn't one and do a CFI attack. // // To make these harder we want to make sure this is a class that was // either built into the binary or legitimately registered through // objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair. // // TODO: This check is quite costly during process startup. /* Ensures that the class is already loaded */ checkIsKnownClass(CLS); /* If (slowpath(!) slowpath(!) = slowpath(!) cls->isRealized())) { cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock); // runtimeLock may have been dropped but is now locked again} Slowpath ((behavior & LOOKUP_INITIALIZE) &&! cls->isInitialized())) { cls = initializeAndLeaveLocked(cls, inst, runtimeLock); // runtimeLock may have been dropped but is now locked again // 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 } runtimeLock.assertLocked(); curClass = cls; // The code used to lookpu the class's cache again right after // we take the lock but for the vast majority of the cases // evidence shows this is a miss most of the time, hence a time loss. // // The only codepath calling into this without having performed some // kind of cache lookup is Class_getInstanceMethod (). For (unsigned attempts = unreasonableClassCount();) {// curClass method list. // Current class method list (binary search algorithm), if found, return, Method meth = getMethodNoSuper_nolock(curClass, sel); if (meth) { imp = meth->imp; goto done; } // Current class = parent of current class, If (slowPath ((curClass = curClass->superclass) == nil)) {// No implementation found, And method resolver didn't help. // Use forwarding. break; } // If there is a cycle in the superclass chain, If (slowpath(--attempts == 0)) {_objc_fatal("Memory corruption in class list "); } // Superclass cache. // 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. } if (fastPath (imp)) {// Found the method in a superclass. Cache it in this class. Goto done; }} No implementation found. Try method resolver once. Behavior ^= LOOKUP_RESOLVER; if (slowpath(behavior & LOOKUP_RESOLVER)) {// Slowpath (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

There are also the following steps

The first step

If it is found in the cache, it is returned directly. If it is not found in the cache, it enters the slow search process. In fact, there is a question because it has entered the slow search process. This is actually an optimization to prevent you from having to go through a slow lookup process when you call this method in a multi-threaded operation.

The second step is to judge CLS first

  • Determine whether CLS is a known class, that is, ensure that CLS is already loaded

  • Determine whether the class is implemented, if not to achieve, here is to ensure the call of the parent chain method, the purpose of the implementation is to determine the parent chain, RO, and RW, method subsequent data reading and search cycle

  • Determines whether the class is initialized, and if not initialized first

Third step began to loop through, according to the kind of endless chain (class — — — > parent – > root class – > nil) and metaclass circulation chain (metaclass – > root class – > root class – > nil) began to loop through

  • A list of the current class’s methods (using a binary search algorithm), if found, returns the method to cache

  • Current class = the parent of the current class, and determine if the parent is nil, if nil, IMP = forward_IMP, forward the message (step 4), and end the loop

  • Stop if there is a loop in the parent chain

  • Look it up in the parent cache

  1. If found, returnimpAnd writecache
  2. If you find it in the parent classforward, the lookup is stopped and no caching is performed. The method resolver of this class is called first
  3. If not, the search continues in a loop

The fourth step

Determines whether dynamic method resolution has been performed. If not, dynamic method resolution is performed once. If dynamic method resolution has been performed, the message forwarding process is carried out.

conclusion

  • Instance methodsinSlow to findIs in the process ofclassLook for,The chain of the parent classforClass -- parent class -- root class --nil
  • Class methodinSlow to findIs in the process ofThe metaclassLook for,The chain of the parent classforMetaclass -- root class -- root class --nil
  • ifQuickly find,Slow to findI didn’t find it.Method implementation, then try toDynamic method resolution
  • ifDynamic method resolutionIf still not found, proceedforward