Write in the front: the exploration of the underlying principles of iOS is my usual development and learning in the continuous accumulation of a step forward road. I hope it will be helpful for readers to record my journey of discovery.Copy the code

The directory is as follows:

  1. Exploring the underlying principles of iOS alloc
  2. Exploration of the underlying principles of iOS structure in vivo alignment
  3. The nature of the object explored by iOS underlying principles & the underlying implementation of ISA
  4. The underlying principles of the ISA-class (part 1)
  5. The underlying principles of the ISA class (middle)
  6. The underlying principles of isA-Class (part 2)
  7. Exploring the nature of Runtime Runtime & Methods in iOS Underlying principles
  8. Objc_msgSend explores the underlying principles of iOS

Summary column for the above

  • Summary of the phase of iOS underlying principle exploration

preface

Last time, we explored the entire process of objc_msgSend. One question is, why write objc_msgSend in a assembly? Because the whole process of assembly execution is closer to the speed of machine language, fast, and safe, the caching mechanism is nothing more than to achieve a little more efficient, a little faster, and can be more dynamic. So why the return to c++? Because it’s not found in the cache, you’re going to go through a slow lookup process, and you’re going to iterate through the methodList. It’s going to be time consuming. The end result is bl _lookUpImpOrForward, which is essentially a jump from assembly to c++. IMP lookUpImpOrForward(ID INST, SEL SEL, Class CLS, int Behavior)

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)

LookUpImpOrForward to query the imp based on sel

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, Int behaviors) {/ / define the message forwarding forward_imp / / behaviors of the incoming is 3 = LOOKUP_INITIALIZE | LOOKUP_RESOLVER const IMP forward_imp = (IMP)_objc_msgForward_impcache; IMP imp = nil; Class curClass; runtimeLock.assertUnlocked(); if (slowpath(! CLS - > isInitialized ())) {/ / whether the class initialization Uninitialized behaviors = LOOKUP_NOCACHE | LOOKUP_INITIALIZE | LOOKUP_RESOLVER / / sent to the class of the first message is usually + new + alloc or + self behaviors | = LOOKUP_NOCACHE; } // Prevent concurrent implementation races. runtimeLock.lock(); // Check whether it is a registered class loaded by dyld or by a legally registered class checkIsKnownClass(CLS); Walk / / implementation classes including the isa bit of parent and metaclass / / initialize the class and the parent class CLS = realizeAndInitializeIfNeeded_locked (inst, CLS, behaviors & LOOKUP_INITIALIZE); runtimeLock.assertLocked(); curClass = cls; // We then take the lock for the code used to look up the class cache again, // but in most cases the evidence shows that this is missing in most cases, and therefore a loss of time. // The only code path that calls this without performing some class cache lookup is class_getInstanceMethod(). for (unsigned attempts = unreasonableClassCount();;) {// check whether there is a shared cache. General method doesn't go the if (curClass - > cache. IsConstantOptimizedCache strict (/ * * / true)) {# if CONFIG_USE_PREOPT_CACHES / * query again Shared cache, */ imp = cache_getImp(curClass, sel); If (IMP) goto done_unlock; curClass = curClass->cache.preoptFallbackClass(); #endif} else {// curClass methodlist. // Use binary search algorithm to find methodList method meth = getMethodNoSuper_nolock(curClass, sel); If (methamphetamine) {methamphetamine = methamphetamine -> methamphetamine = methamphetamine -> methamphetamine (false); // Get imp goto done; } // curClass = curClass->getSuperclass(); // curClass = curClass->getSuperclass(); If (slowPath ((curClass = curClass->getSuperclass()) == nil)) { Assign forward_IMP to IMP imp = forward_IMP; break; } // Stop if (slowPath (--attempts == 0)) {_objc_fatal("Memory corruption in class list."); } cache_getImp(curClass, sel); If (slowPath (IMP == forward_IMP)) {// The forward:: entry was found in the superclass. // Stop searching, but do not cache; Calls the method to this class's parser. // Call the parser of this class. break; } if (fastPath (imp)) {// find the method in the superclass. Cache it goto done in this class; }} // Not implemented. Try the method parser 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 // Insert sel (CLS, IMP, sel, inst, curClass) into the cache; } done_unlock: runtimeLock.unlock(); /* if (behavior & LOOKUP_NIL) is true If (slowPath ((behavior & LOOKUP_NIL) && imp == forward_IMP))) {return nil; } return imp; }Copy the code

Slow search process

  • Check if it is a registered class. If not, report an error.
  • Implements the given class if it is not already initialized, or initializes it if it is not. Inst is an instance or a subclass of CLS, or nil if you don’t know. CLS is the class to initialize and implement. The initializer is true to initialize the class and false to skip initialization.

CLS query

  • Check whether it exists in the shared cache, because it’s possible that this method was called during the lookup and cached,
  • Use the binary search algorithm in your class, look for methodList, and if you find it, insert it into the cache. End of loop

In the cache of the parent class

  • If there is a loop in the superclass chain, the query is terminated and the loop is broken
  • Assign superclass to curClass, look in the parent class, and if forward_IMP is returned in the parent class, the traversal is jumped and the message is forwarded.
  • If the superclass is not found in the parent class, then assign superclass to curClass again and search in the parent class until curClass == nil, IMP = forward_IMP and forward the message

Dynamic method resolution

  • If the IMP is not found in the CLS and the parent class chain, the system will give a chance to determine whether the dynamic method resolution has been made. If not, the dynamic method resolution will be taken. If the method has been executed, the message will be forwarded.

The flow chart of lookUpImpOrForward

Binary search algorithm

/*********************************************************************** * search_method_list_inline **********************************************************************/ template<class getNameFunc> ALWAYS_INLINE static  method_t * findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName) { ASSERT(list); Auto first = list->begin(); auto base = first; decltype(first) probe; Uintptr_t keyValue = (uintPtr_t)key; // uintptr_t keyValue = (uintptr_t)key; uint32_t count; Count = number of arrays count >>= 1 = count = count >> 1) Count = list->count = 8 //2 count = 7 >> 1 = 3 for (count = list->count; count ! = 0; Count >>= 1) {/* 1. Probe = base + 4 2. */ probe = base + (count >> 1); // UintPtr_t probeValue = (uintPtr_t)getName(probe); If (keyValue == probeValue) {if (keyValue == probeValue) {if (keyValue == probeValue) {if (keyValue == probeValue) {if (keyValue == probeValue) { While (uintptr_t)getName((probe-1)) {probe--; } // Return the address of the method return &*probe; If (keyValue > probeValue) {/* 1. Base = probe + 1 = 4 + 1 = base (first address) + 5 move up one bit 2 probe + 1 ; */ base = probe + 1; // 8 -1 = 7; // return nil; // Return nil; }Copy the code

The above code is binary search code, really 🐂🍺, so, the code is not intuitive enough, we try to use a few pictures to illustrate the above process, let everyone to appreciate the next binary search charm.

conclusion

The process of finding methods is actually a very complex process. Now we have only discussed the fast compilation super search process and slow super search process, and dynamic method resolution, as well as the message forwarding process, everyone come on!