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-C
The 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 passsel
Find the implementation of the methodIMP
?
You can see that from hereObjective-C
The 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 instance
A method is called
objc_msgSend(p, @selector(run));
class
A 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 class
Calling 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 class
Calling 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 finding
The cache
In the assembly - The slow way:
C
,C++
Find together with the assembly
cache
Contained in thesel
andIMP
The cache will look for a file fromsel
andIMP
Hash 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.LNilOrTagged
This step determines whether the current object isTagged-Point
Object, which I won’t explain here 2.LGetIsaDone
rightIsa
Proceed to process 3.CacheLookup NORMAL
Find 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 isNORMAL
Have managed__objc_msgSend_uncached
MethodTableLookup
After 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.
CacheLookup
Quick 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 the
cache
The structure ofmask
Account forHigh 16
.buckets
inLower 48
, so adopt and0x0000ffffffffffff
Carry out and operation to clear the high 16 bits, directly getbuckets
depositp10
- The same will be
cache
Moves to the right48
Get the height directly16
themask
That will bep1
In thesel
withmask
And we getsel-imp
The subscriptIndex (search subscript)
The results inp12
And 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
-
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.
-
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
lookUpImpOrForward
Slow 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
- If found, return
imp
And writecache
- If you find it in the parent class
forward
, the lookup is stopped and no caching is performed. The method resolver of this class is called first - 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 methods
inSlow to find
Is in the process ofclass
Look for,The chain of the parent class
forClass -- parent class -- root class --nil
Class method
inSlow to find
Is in the process ofThe metaclass
Look for,The chain of the parent class
forMetaclass -- root class -- root class --nil
- if
Quickly find
,Slow to find
I didn’t find it.Method implementation
, then try toDynamic method resolution
- if
Dynamic method resolution
If still not found, proceedforward