Nature of method

In the previous article, The Nature of Objects, Clang was used to explore the nature of objects. We also use Clang to get the.cpp file to see the implementation of the method in main.

// HTPerson *person = [HTPerson alloc]; [person sayNB]; [Person playGame:@" King of Glory "]; HTPerson *person = ((HTPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("HTPerson"), sel_registerName("alloc")); ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB")); ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("playGame:"), (NSString *)&__NSConstantStringImpl__var_folders_pb_ltmc_s613_76jpz77vf9cvx40000gn_T_main_dad2e6_mi_3);Copy the code

As you can see, the method is actually an objc_msgSend message, and the method execution, in fact, is to find the implementation address of the method, and the process of executing the method.

Objc_msgSend (id _Nullable self, SEL _Nonnull op,...)Copy the code

Upon receiving the objc_msgSend message, the object looks for the implementation address of the method based on the class that ISA points to. The search process is roughly as follows:

  • Quick lookup: Cache of visited classescacte_tIf there is a corresponding method implementation, return the method address.
  • Slow lookup: If there is no cache, the lookup will be donebitsIn themethods. If I don’t go onsuperclassFind the corresponding method.

Quick query of methods (get methods in cache)

A search for objc_msgSend in the objC source will see ENTRY _objc_msgSend in the objC-msG-arm64 file. Here is the query for the method using assembly. The general process is as follows:

  • judgeobjc_msgSendThe first argument to the methodreceiverWhether it is empty and getsisa
  • Go to quick method queryCacheLookup
  • Translation by 16 bytes through the header address (because inobjc_class, the first address is exactly 16 bytes from the cache, that is, isa first address is 8 bytes, superClass is 8 bytes)cahce
  • Respectively from the cachebucketsandmaskAnd the hash subscript is calculated by Mask according to the hash algorithm
  • Recursively query the corresponding bucketselAnd in the messageopIf yes, the query is returned. If no, enter the slow query method (__objc_msgSend_uncached)

Method for slow queries

The main process of slow query:

  • From the __objc_msgSend_uncached method in the compile to the bottomlookUpImpOrForwardThe implementation of the
  • LookUpImpOrForward does three things
    • Initialize theAll the parent classes
    • Infinite loopA binary method to find a class, find the method of the parent class cache, again dichotomy query the method of the parent class…
    • Get the method impThe cacheIn the cache

Initialize all parent classes

Methods realizeAndInitializeIfNeeded_locked point there is a realization method. Determine whether the class is realized. If not, it needs to be realized first and determine the parent class chain. At this time, the purpose of instantiation is to determine the parent class chain, RO, and RW, etc., and the subsequent data reading and search cycle of the method

A binary method to find a class

Method getMethodNoSuper_nolock. Will find

  • According to the method of moving one bit to the right of binary, binary query is realized
  • If a method has the same name, it returns the method name that precedes it.

The imp is cached in the cache

Method log_and_fill_cache imp of the cache method. If the IMP is not found, dynamic method resolution and message forwarding is performed. This will be covered in the next section.

conclusion

  • The order in which class methods are executed is determined by the order of the query. (Subclass – Superclass – NSObject)
  • In binary queries, it is decided to perform the classification method first.
  • Dynamic method resolution is tried when no method is found
  • If the dynamic method resolution is still not found, the message is forwarded

The source code is shown below

-lookupimporForward -slow -methodList // assembly cache parameter unknown NEVER_INLINE IMP lookUpImpOrForward(id inst, SEL SEL, Class CLS, int behavior) {// create forward_imp, And given the default value _objc_msgForward_impcache const IMP forward_IMP = (IMP)_objc_msgForward_impcache; // create imp for sel lookup imp imp = nil; // Create the Class to look for, which changes through isa until it points to NSObject's parent Class nil Class curClass; runtimeLock.assertUnlocked(); // The first message sent to the class is usually +new or +alloc or +self // However, the class has not yet been initialized, The behaviors = 3 = | 8 + new on 11 / / when will these methods, such as when an inset into the cache / / does not meet the behaviors & LOOKUP_NOCACHE) = = 0 this condition, 8&11 = 8 // So these methods are not loaded into the cache. // // If the class is already initialized, behavior=3 will not be changed, behavior=3 // Our custom method can be loaded into the cache. if (slowpath(! cls->isInitialized())) { behavior |= LOOKUP_NOCACHE; } // runtimeLock is held during isRealized and isInitialized checks // locked - to prevent competing with concurrent implementations. runtimeLock.lock(); // Check if it is a registered class checkIsKnownClass(CLS); // Initializes CLS instance objects in each class (class and metaClass) in the ISA pointing diagram so that there is no method to find in the parent class in the next class // up // so all related classes are initialized here CLS = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE); // runtimeLock may have been dropped but is now locked again runtimeLock.assertLocked(); // curClass = CLS; // Find the parent class if the parent class does not have one, Always find NSObject class // If NSObject is not found, eventually curClass will point to nil // assign the prepared forward_IMP to IMP // and end the slow lookup process, Reasonableclasscount (); {/ / in the first step to look for the Shared cache our method / / our custom method normally won't appear in the Shared cache 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(); Method meth = getMethodNoSuper_nolock(curClass, sel); // Method meth = getMethodNoSuper_nolock(curClass, sel); // if (meth) {imp = meth->imp(false); // Jump to done goto done; } // If the current class is not found, take a methodList that points curClass to superclass and query the parent class until you find NSObject's parent class nil. // Assign the prepared forward_IMP to IMP and end the slow lookup process, 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)) {// Cache // Found the method in a superclass. Cache it in this class.goto done; }} // 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:// Cache 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: // unlock runtimelock.unlock (); if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) { return nil; } return imp; }Copy the code

The slow query flow chart is as follows

The binary query is implemented as follows

Reference: month-month method fast query and slow query BBLV fast query