Runtime API provides the interface is basically C language, source code written by C++ assembly language
Introduction to the
- xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
- main.m -> main.cpp
- Main. m calls a function
- C++ file execution
-
[person Persontest] essentially calls objc_msgSend(person,sel_registerName(“persontest”))
-
Sel_registerName (“persontest”) is a C function of the Runtime equivalent to @selector(persontest)
-
So objc_msgSend (person, @ the selector (personTest))
-
The same is true for class method calls
Objc_msgSend profile
- The OC method calls are converted to the objc_msgSend function
- OC method call: sends a message to the message recipient
MJPerson *person = [[MJPerson alloc] init]; [person personTest]; // objc_msgSend(person, @selector(personTest)); // Message name: personTest [MJPerson initialize]; // objc_msgSend([MJPerson class], @selector(initialize)); [MJPerson class] // Message name: initializeCopy the code
- The execution process of objc_msgSend can be divided into three phases
- Message is sent
- Dynamic method parsing
- forward
Objc_msgSend View the source code for sending messages
- Objc_msgSend assembly source code concrete implementation
_objc_msgSend ENTRY _objc_msgSend UNWIND _objc_msgSend, NoFrame p0: p0 p0: the first argument to objc_msgSend is Self CMP p0 is null, Nil check and tagged pointer check #if SUPPORT_TAGGED_POINTERS // If SUPPORT_TAGGED_POINTERS is supported Check whether the comparison result is less than or equal to 0. If yes, it goes to LNilOrTagged. Under ARM64, when it is Tagged pointer, the highest bit is 1, which is the signed number < 0. // If not, check whether the comparison result is 0. If 0, jump to LReturnZero for nil processing. B.llnilortagged // (MSB tagged pointer looks negative) #else b.lreturnZero #endif P0 and x0 are equivalent to x0 = self because isa is //objc_object has only one member ISA, so fetching what the pointer points to gets the value of ISA. // p13 = isa ldr p13, [x0] GetClassFromIsa_p16 p13, 1, x0 // p16 = class LGetIsaDone: // calls imp or objc_msgSend_uncached CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached // Query cached #if SUPPORT_TAGGED_POINTERS LNilOrTagged: b.eq LReturnZero // nil check GetTaggedClass b LGetIsaDone // SUPPORT_TAGGED_POINTERS #endif LReturnZero: // x0 is already zero mov x1, #0 movi d0, #0 movi d1, #0 movi d2, #0 movi d3, #0 ret END_ENTRY _objc_msgSendCopy the code
- Cache lookups simplify
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant x16 // stash the original isa LLookupStart\Function: // p1 = SEL, P16 = isa #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS # CACHE] / / p10 = mask | buckets / / put cache_t address in p11 logic moves to the right, P11, p10, #48 // p11 = mask p10, p10, p10 // p10 = buckets // SEL CMD: SEL & Mask to calculate the corresponding index and w12, w1, w11 // x12 = _cmd & mask #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 ldr p11, [x16, #CACHE] // p11 = mask|buckets #if CONFIG_USE_PREOPT_CACHES #if __has_feature(ptrauth_calls) tbnz p11, #0, LLookupPreopt\Function and p10, p11, #0x0000ffffffffffff // p10 = buckets #else and p10, p11, #0x0000fffffffffffe // p10 = buckets tbnz p11, #0, LLookupPreopt\Function #endif eor p12, p1, p1, LSR #7 and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask #else and p10, p11, #0x0000ffffffffffff // p10 = buckets and p12, p1, p11, LSR #48 // x12 = _cmd & mask #endif // CONFIG_USE_PREOPT_CACHES #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4 ldr p11, [x16, #CACHE] // p11 = mask|buckets 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 Add p13, p10, p12, p13, p13 LSL #(1+PTRSHIFT) p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT)) LSL #(1+PTRSHIFT) p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT)) Where P17 is IMP, p9 is SEL. 1: LDP p17, p9, [x13], # -bucket_size // {imp, sel} = *bucket-- } else {2: CacheHit \Mode // hit: Call or return IMP //} // check whether SEL in P9 is empty. If not, go to __objc_msgSend_uncached. cbz p9, \MissLabelDynamic // if (sel == 0) goto Miss; // if (bucket >= buckets) // if (bucket >= buckets) // if (bucket >= buckets) // if (bucket >= bucketsCopy the code
- Find the address of the return IMP method in the cache
- Because the first parameter is normal
- X17 = cache IMP
- X10 = address of cache entry found
- x1 = sel
- x16 = class
- Methodtable – ookup is called if no method is found in the cache
.macro save SAVE_REGS MSGSEND // lookUpImpOrForward(obj, SEL, CLS, LOOKUP_INITIALIZE | LOOKUP_RESOLVER) / / receiver and the selector already in x0 and x1 / / preserved the CLS mov x2 x2, X16 // LOOKUP_INITIALIZE = 1, LOOKUP_RESOLVER = 2, x16 // LOOKUP_INITIALIZE = 1, LOOKUP_RESOLVER = 2, x16 // LOOKUP_INITIALIZE = 1, LOOKUP_RESOLVER = 2, // imp in x0 // imp in x0 // imp in x17 mov x17 X0 // Restore register RESTORE_REGS MSgsend.endmacroCopy the code
- Bosses articles
Big guy article can refer to
- The _lookUpImaOrForward assembly method can only be found in C without the underscore
NEVER_INLINE 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(); if (slowpath(! cls->isInitialized())) { behavior |= LOOKUP_NOCACHE; } runtimeLock.lock(); checkIsKnownClass(cls); cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE); runtimeLock.assertLocked(); curClass = cls; 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; } } // No implementation found. Try method resolver once. if (slowpath(behavior & LOOKUP_RESOLVER)) { behavior ^= LOOKUP_RESOLVER; return resolveMethod_locked(inst, sel, cls, behavior); } done: if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) { #if CONFIG_USE_PREOPT_CACHES while (cls->cache.isConstantOptimizedCache(/* strict */true)) { cls = cls->cache.preoptFallbackClass(); } #endif log_and_fill_cache(cls, imp, sel, inst, curClass); } done_unlock: runtimeLock.unlock(); if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) { return nil; } return imp; }Copy the code
- Please refer to this big guy’s article for details because I use 818.2 source code and his use 718.2 so it’s a bit different.
Refer to the article
- The following process is obtained:
- 1. Own cache ->
- 2. My own methods ->
- 3. Father’s cache ->
- 4. Father’s Methods ->
- 5. Father’s father’s cache ->
- 6. Father’s Father’s Methods ->
- 7. Nil -> resolveMethod_locked
- If you’re looking for a method from class_rw_t
- Sorted binary search
- If it’s not sorted, go through it
- The Receiver finds the receiverClass through the ISA pointer
- ReceiverClass finds the SuperClass using the Superclass pointer