Runtime series
Run time (1): first step
Run time (2): data structure
Run time (3): message mechanism
On the essence of Runtime (4): super
Run time (5): specific application
Run time (6): related interview questions
1. Call process of objc_msgsend method
- The method calls in OC are actually all the calls converted to objc [msgsend() function (excluding [super message]).
void objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
- Send a message (SEL method name) to the receiver (method caller / message receiver)
Parameter 1: receiver
Parameter 2: sel
Parameters 3, 4, 5 : parameters of SEL method -
The execution process of objc ﹣ msgsend() can be divided into three stages:
message sending
Dynamic method analysis
Message forwarding
2. Message sending
Message sending process
Source code analysis
As mentioned in the previous article, Runtime is a Runtime library written in C and assembly,
If the C function needs to be called in the underlying assembly, Apple will underline it,
Therefore, to view the implementation of objc ﹣ msgsend function, you need to search for ﹣ objc ﹣ msgsend (objc-msg-arm64.s (objc4)).
// objc-msg-arm64.s(objc4) /* _objc_msgSend Function realization */ // A kind of Assembler ENTRY format: ENTRY + function name ENTRY _objc_msgSend // A kind of If the receiver is nil or tagged pointer, execute LNilOrTagged, otherwise continue to execute cmp x0, #0 // nil check and tagged pointer check b.le LNilOrTagged // A kind of Find class / meta class through isa ldr x13, [x0] // x13 = isa and x16, x13, #ISA_MASK // x16 = class LGetIsaDone: // A kind of Enter the cache to search, and the parameter passed is NORMAL // CacheLookup macro, used to find SEL corresponding method implementation in cache CacheLookup NORMAL // calls imp or objc_msgSend_uncached LNilOrTagged: // A kind of If the receiver is nil, run LReturnZero to end objc ﹐ msgsend b.eq LReturnZero // nil check // A kind of If the receiver is tagged pointer, execute other ...... b LGetIsaDone LReturnZero: ret // Return // A kind of In assembly, the end format of the function is: ENTRY + function name END_ENTRY _objc_msgSend .macro CacheLookup // A kind of - search methods in hash table buckets according to SEL // x1 = SEL, x16 = isa ldp x10, x11, [x16, #CACHE] // x10 = buckets, x11 = occupied|mask and w12, w1, w11 // x12 = _cmd & mask add x12, x10, x12, LSL #4 // x12 = buckets + ((_cmd & mask)<<4) ldp x9, x17, [x12] // {x9, x17} = *bucket // A kind of - cache hit, cache hit 1: cmp x9, x1 // if (bucket->sel != _cmd) b.ne 2f // scan more CacheHit $0 // call or return imp // A kind of No cache found, CheckMiss operation 2: // not hit: x12 = not-hit bucket CheckMiss $0 // miss if bucket->sel == 0 cmp x12, x10 // wrap if bucket == buckets b.eq 3f ldp x9, x17, [x12, #-16]! // {x9, x17} = *--bucket b 1b // loop 3: // wrap: x12 = first bucket, w11 = mask add x12, x12, w11, UXTW #4 // x12 = buckets+(mask<<4) // Clone scanning loop to miss instead of hang when cache is corrupt. // The slow path may detect any corruption and halt later. ldp x9, x17, [x12] // {x9, x17} = *bucket 1: cmp x9, x1 // if (bucket->sel != _cmd) b.ne 2f // scan more CacheHit $0 // call or return imp 2: // not hit: x12 = not-hit bucket CheckMiss $0 // miss if bucket->sel == 0 cmp x12, x10 // wrap if bucket == buckets b.eq 3f ldp x9, x17, [x12, #-16]! // {x9, x17} = *--bucket b 1b // loop 3: // double wrap JumpMiss $0 .endmacro // CacheLookup NORMAL|GETIMP|LOOKUP #define NORMAL 0 #define GETIMP 1 #define LOOKUP 2 .macro CacheHit .if $0 == NORMAL // A kind of The parameter of CacheLookup is NORMAL MESSENGER_END_FAST br x17 // call imp // A kind of Execution function .elseif $0 == GETIMP mov x0, x17 // return imp ret .elseif $0 == LOOKUP ret // return imp via x17 .else .abort oops .endif .endmacro .macro CheckMiss // miss if bucket->sel == 0 .if $0 == GETIMP cbz x9, LGetImpMiss .elseif $0 == NORMAL // A kind of The parameter of CacheLookup is NORMAL cbz x9, __objc_msgSend_uncached // A kind of Run objc msgsend uncached .elseif $0 == LOOKUP cbz x9, __objc_msgLookup_uncached .else .abort oops .endif .endmacro .macro JumpMiss .if $0 == GETIMP b LGetImpMiss .elseif $0 == NORMAL b __objc_msgSend_uncached .elseif $0 == LOOKUP b __objc_msgLookup_uncached .else .abort oops .endif .endmacro // ⚠️__objc_msgSend_uncached // A kind of There is no method implementation found in the cache. Next, go to the method list of MethodTableLookup class to find STATIC_ENTRY __objc_msgSend_uncached MethodTableLookup NORMAL END_ENTRY __objc_msgSend_uncached .macro MethodTableLookup blx __class_lookupMethodAndLoadCache3 // A kind of Run the C function, class, lookupmethodandloadcache3 .endmacro
On the contrary, when finding the corresponding C function through the function name in the assembly, you need to remove an underscore.
// objc-runtime-new.mm(objc4) IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls) { // A kind of Please pay attention to the parameters. Since the assembly has been used to search in the cache before, we will not search in the cache again return lookUpImpOrForward(cls, sel, obj, YES/*initialize*/, NO/*cache*/, YES/*resolver*/); } IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) { IMP imp = nil; bool triedResolver = NO; // triedResolver tag for dynamic method parsing runtimeLock.assertUnlocked(); // Optimistic cache lookup if (cache) { // cache = NO, skip imp = cache_getImp(cls, sel); if (imp) return imp; } // 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 flush on // behalf of the category. runtimeLock.read(); if (!cls->isRealized()) { // A kind of If the receiverclass has not been implemented, perform the realize operation // Drop the read-lock and acquire the write-lock. // realizeClass() checks isRealized() again to prevent // a race while the lock is down. runtimeLock.unlockRead(); runtimeLock.write(); realizeClass(cls); runtimeLock.unlockWrite(); runtimeLock.read(); } // A kind of If the receiverClass needs to be initialized and has not yet been initialized, initialize it // Insert a knowledge point of + initialize method here // Call ﹣ class ﹣ initialize (CLS), which will recursively traverse the parent class to determine whether the parent class exists and has not been initialized ﹣ class ﹣ initialize (CLS - > superclass) // Call call initialize (cls) and send an initialize message ((void (*) (class, SEL)) objc ﹣ msgsend) (cls, sel ﹣ initialize) to cls // So the + initialize method is called the first time the class receives a message // Call method: objc_msgSend() // Call order: first call + initialize of the parent class, then + initialize of the child class (first initialize the parent class, then the child class, each class will only be initialized once) if (initialize && !cls->isInitialized()) { runtimeLock.unlockRead(); _class_initialize (_class_getNonMetaClass(cls, inst)); runtimeLock.read(); // 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 } // A kind of A kind of A kind of A kind of A kind of Core retry: runtimeLock.assertReading(); // A kind of Go to the cache of receiverClass to find the method. If imp is found, call it directly imp = cache_getImp(cls, sel); if (imp) goto done; // A kind of Go to the method list in receiverClass's class \ RW \ t to find the method. If imp is found, call it and cache the method in receiverClass's cache { Method meth = getMethodNoSuper_nolock(cls, sel); // A kind of Go to the method list of the target class to find the method implementation if (meth) { log_and_fill_cache(cls, meth->imp, sel, inst, cls); // A kind of Caching method imp = meth->imp; goto done; } } // A kind of Level by level search the cache and method list of the parent class. If imp is found, call and cache the method in the cache of receiverClass { unsigned attempts = unreasonableClassCount(); for (Class curClass = cls->superclass; curClass != nil; curClass = curClass->superclass) { // Halt if there is a cycle in the superclass chain. if (--attempts == 0) { _objc_fatal("Memory corruption in class list."); } // Superclass cache. imp = cache_getImp(curClass, sel); if (imp) { if (imp != (IMP)_objc_msgForward_impcache) { // Found the method in a superclass. Cache it in this class. log_and_fill_cache(cls, imp, sel, inst, curClass); goto done; } else { // Found a forward:: entry in a superclass. // Stop searching, but don't cache yet; call method // resolver for this class first. break; } } // Superclass method list. Method meth = getMethodNoSuper_nolock(curClass, sel); if (meth) { log_and_fill_cache(cls, meth->imp, sel, inst, curClass); imp = meth->imp; goto done; } } } // A kind of Enter the stage of "dynamic method analysis" // No implementation found. Try method resolver once. if (resolver && !triedResolver) { runtimeLock.unlockRead(); _class_resolveMethod(cls, sel, inst); runtimeLock.read(); // Don't cache the result; we don't hold the lock so it may have // changed already. Re-do the search from scratch instead. triedResolver = YES; goto retry; } // A kind of Enter the stage of "message forwarding" // No implementation found, and method resolver didn't help. // Use forwarding. imp = (IMP)_objc_msgForward_impcache; cache_fill(cls, sel, imp, inst); done: runtimeLock.unlockRead(); return imp; }
Let's take a look at how getmethodnoseuper_nolock (CLS, SEL) looks up a method implementation from a class
- If the method list is sorted, binary search is performed;
- If the method list is not sorted, a linear traversal lookup is performed.
static method_t * getMethodNoSuper_nolock(Class cls, SEL sel) { runtimeLock.assertLocked(); assert(cls->isRealized()); // fixme nil cls? // fixme nil sel? for (auto mlists = cls->data()->methods.beginLists(), end = cls->data()->methods.endLists(); mlists != end; ++mlists) { // A kind of Core function search method list () method_t *m = search_method_list(*mlists, sel); if (m) return m; } return nil; } static method_t *search_method_list(const method_list_t *mlist, SEL sel) { int methodListIsFixedUp = mlist->isFixedUp(); int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t); if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) { // A kind of If the method list is sorted, perform binary search return findMethodInSortedMethodList(sel, mlist); } else { // A kind of If the method list is not sorted, perform a linear traversal search // Linear search of unsorted method list for (auto& meth : *mlist) { if (meth.name == sel) return &meth; } } ...... return nil; } static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list) { assert(list); const method_t * const first = &list->first; const method_t *base = first; const method_t *probe; uintptr_t keyValue = (uintptr_t)key; uint32_t count; // A kind of : count > > = 1 binary search for (count = list->count; count != 0; count >>= 1) { probe = base + (count >> 1); uintptr_t probeValue = (uintptr_t)probe->name; if (keyValue == probeValue) { // `probe` is a match. // Rewind looking for the *first* occurrence of this value. // This is required for correct category overrides. while (probe > first && keyValue == (uintptr_t)probe[-1].name) { probe--; } return (method_t *)probe; } if (keyValue > probeValue) { base = probe + 1; count--; } } return nil; }
Let's take a look at how log and fill cache (CLS, meth->imp, SEL, Inst, CLS) caches methods
/*********************************************************************** * log_and_fill_cache * Log this method call. If the logger permits it, fill the method cache. * cls is the method whose cache should be filled. * implementer is the class that owns the implementation in question. **********************************************************************/ static void log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer) { #if SUPPORT_MESSAGE_LOGGING if (objcMsgLogEnabled) { bool cacheIt = logMessageSend(implementer->isMetaClass(), cls->nameForLogging(), implementer->nameForLogging(), sel); if (!cacheIt) return; } #endif cache_fill (cls, sel, imp, receiver); } #if TARGET_OS_WIN32 || TARGET_OS_EMBEDDED # define SUPPORT_MESSAGE_LOGGING 0 #else # define SUPPORT_MESSAGE_LOGGING 1 #endif
The implementation of the cache_fill() function has been written in the previous article.
For the cache lookup process and more information about cache_t, you can view:
Runtime (two): data structure
2. Dynamic method analysis
"Dynamic method analysis" process
- If the implementation of the method is not found in the "message sending" phase, a "dynamic method parsing" is performed;
- After "dynamic method parsing", it will enter the "message sending" process again,
Start from the step of "find method in cache of receiverClass". - We can override the following methods based on the method type (instance method or class method)
+(BOOL)resolveInstanceMethod:(SEL)sel
+(BOOL)resolveClassMethod:(SEL)sel
The following methods are invoked in the method to dynamically add the implementation of the method.
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) - The example code is as follows. We call the eat instance method and class method of HTPerson respectively, but there is no corresponding implementation of these two methods in HTPerson.m file. We dynamically add the implementation for these two methods, and the output results are as follows.
// main.m #import <Foundation/Foundation.h> #import "HTPerson.h" int main(int argc, const char * argv[]) { @autoreleasepool { [[HTPerson new] eat]; [HTPerson eat]; } return 0; } @end // HTPerson.h #import <Foundation/Foundation.h> @interface HTPerson : NSObject - (void)eat; // No corresponding implementation - (void)sleep; + (void)eat; // No corresponding implementation + (void)sleep; @end // HTPerson.m #import "HTPerson.h" #import <objc/runtime.h> @implementation HTPerson - (void)sleep { NSLog(@"%s",__func__); } + (void)sleep { NSLog(@"%s",__func__); } + (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(eat)) { // Get other methods. Method is the pointer to the structure of method Method method = class_getInstanceMethod(self, @selector(sleep)); /* Parameter 1: which class to add Parameter 2: which method to add Parameter 3: implementation address of the method Parameter 4: encoding type of method */ class_addMethod(self, // Instance methods are stored in class objects, so class objects are passed in here sel, method_getImplementation(method), method_getTypeEncoding(method) ); // Return YES to represent the implementation of dynamic adding method // From the source code point of view, this return value is only used to print the information related to parsing results, and does not affect the results of dynamic method parsing return YES; } return [super resolveInstanceMethod:sel]; } + (BOOL)resolveClassMethod:(SEL)sel { if (sel == @selector(eat)) { Method method = class_getClassMethod(object_getClass(self), @selector(sleep)); class_addMethod(object_getClass(self), // Class methods are stored in metaclass objects, so metaclass objects are passed in here sel, method_getImplementation(method), method_getTypeEncoding(method) ); return YES; } return [super resolveClassMethod:sel]; } @end
-[HTPerson sleep]
+[HTPerson sleep]
Source code analysis
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) { IMP imp = nil; bool triedResolver = NO; ...... retry: ...... // A kind of If the method implementation is not found in the "message sending" phase, perform a "dynamic method parsing" if (resolver && !triedResolver) { runtimeLock.unlockRead(); _class_resolveMethod(cls, sel, inst); // A kind of Core function runtimeLock.read(); // Don't cache the result; we don't hold the lock so it may have // changed already. Re-do the search from scratch instead. triedResolver = YES; // A kind of Mark triedResolver as YES goto retry; // A kind of Go to message sending again, and start from the step of "search method in cache of receiverClass" } // A kind of Enter the stage of "message forwarding" ...... } // objc-class.mm(objc4) void _class_resolveMethod(Class cls, SEL sel, id inst) { // A kind of Determine whether it is a class object or a meta class object if (! cls->isMetaClass()) { // try [cls resolveInstanceMethod:sel] // A kind of Core function _class_resolveInstanceMethod(cls, sel, inst); } else { // try [nonMetaClass resolveClassMethod:sel] // and [cls resolveInstanceMethod:sel] // A kind of Core function _class_resolveClassMethod(cls, sel, inst); if (!lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) { _class_resolveInstanceMethod(cls, sel, inst); } } } /*********************************************************************** * _class_resolveInstanceMethod * Call +resolveInstanceMethod, looking for a method to be added to class cls. * cls may be a metaclass or a non-meta class. * Does not check if the method already exists. **********************************************************************/ static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst) { // A kind of Check whether the method list of the receiverClass's meta class object contains the SEL? Resolveinstancemethod function imp // A kind of That is to see if we have implemented the + (BOOL)resolveInstanceMethod:(SEL)sel method // A kind of This method must be found here, because there is an implementation in NSObject if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) { // A kind of If it is not found, it indicates that the program is abnormal and returns directly // Resolver not implemented. return; } // A kind of If found, send a sel resolve instancemethod message to the object through objc > msgsend // A kind of Call + (BOOL)resolveInstanceMethod:(SEL)sel method BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; bool resolved = msg(cls, SEL_resolveInstanceMethod, sel); // A kind of Here are some printouts of the results ...... } /*********************************************************************** * _class_resolveClassMethod * Call +resolveClassMethod, looking for a method to be added to class cls. * cls should be a metaclass. * Does not check if the method already exists. **********************************************************************/ static void _class_resolveClassMethod(Class cls, SEL sel, id inst) { assert(cls->isMetaClass()); // A kind of Check the method list of the receiverClass's meta class object to see if there is sel ﹐ resolveclassmethod function imp // A kind of That is to see if we have implemented the + (BOOL)resolveClassMethod:(SEL)sel method // A kind of This method must be found here, because there is an implementation in NSObject if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) { // A kind of If it is not found, it indicates that the program is abnormal and returns directly // Resolver not implemented. return; } // A kind of If found, send a sel resolve class method message to the object through objc \ msgsend // A kind of Call + (BOOL)resolveClassMethod:(SEL)sel method BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; bool resolved = msg(_class_getNonMetaClass(cls, inst), // The return value of this function is a class object, not a metaclass object SEL_resolveClassMethod, sel); // A kind of Here are some printed information of the analysis results ...... }
3. Message forwarding
Message forwarding process
- If the implementation of the method is not found in the "message sending" phase, and it is not solved through "dynamic method parsing",
Enter the "message forwarding" stage; - The "message forwarding" stage is divided into two steps: Fast forwarding and Normal forwarding. As the name suggests, the first step is faster than the second step;
- Fast forwarding: forward a message to another OC object (find an alternate receiver),
We can override the following methods to return an object with! = receiver to complete this step;
+/- (id)forwardingTargetForSelector:(SEL)sel - Normal forwarding: implement a complete message forwarding process,
If the previous step fails to solve the unknown message, you can override the following two methods to start the complete message forwarding.
① The first method: we need to return a method signature suitable for the unknown message in the method (method signature is a description of the return value Type and parameter Type, which can be encoded with Type Encodings. For Type Encodings, please read my previous blog Runtime (two): data structure).
+/- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
Runtime will sign according to this method and create a NSInvocation object (NSInvocation encapsulates all the contents of the unknown message, including: the method caller target, the method name selector, the method parameter argument, etc.), then calls the second method and imports the NSInvocation pair image as the parameter.
② The second method: we can forward the unknown message to other objects, change the content of the unknown message (such as method name, method parameter) and forward it to other objects, or even define any logic.
+/- (void)forwardInvocation:(NSInvocation *)invocation
If there is no return method signature in the first method, or if we do not override the second method, the system will think that we do not want to process the message at all. At this time, the system will call the + / - (void)doesNotRecognizeSelector:(SEL)sel method and throw the classic crash: unrecognized selector send to instance / class to end the whole process of objc_msgSend.
- Let's take a look at the default implementation of these codes
+ (id)forwardingTargetForSelector:(SEL)sel { return nil; } + (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { _objc_fatal("+[NSObject methodSignatureForSelector:] " "not available without CoreFoundation"); } + (void)forwardInvocation:(NSInvocation *)invocation { [self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)]; } + (void)doesNotRecognizeSelector:(SEL)sel { _objc_fatal("+[%s %s]: unrecognized selector sent to instance %p", class_getName(self), sel_getName(sel), self); }
- The Fast forwarding example code is as follows:
We call the eat instance method of HTPerson, but there is no corresponding implementation of the method in HTPerson.m file. HTDog.m has the implementation of the method with the same name. We forward the message to the instance object of HTDog, and the output result is as follows.
// main.m #import <Foundation/Foundation.h> #import "HTPerson.h" int main(int argc, const char * argv[]) { @autoreleasepool { [[HTPerson new] eat]; } return 0; } @end // HTPerson.h #import <Foundation/Foundation.h> @interface HTPerson : NSObject - (void)eat; // No corresponding implementation @end // HTPerson.m #import "HTPerson.h" #import "HTDog.h" @implementation HTPerson - (id)forwardingTargetForSelector:(SEL)aSelector { if (aSelector == @selector(eat)) { return [HTDog new]; // Forward the eat message to the instance object of HTDog // return [HTDog class]; / / you can also forward the eat message to the HTDog class object } return [super forwardingTargetForSelector:aSelector]; } @end // HTDog.m #import "HTDog.h" @implementation HTDog - (void)eat { NSLog(@"%s",__func__); } + (void)eat { NSLog(@"%s",__func__); } @end
-[HTDog eat]
- The Normal forwarding example code and output results are as follows:
// HTPerson.m #import "HTPerson.h" #import "HTDog.h" @implementation HTPerson - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { if (aSelector == @selector(eat)) { return [[HTDog new] methodSignatureForSelector:aSelector]; //return [NSMethodSignature signatureWithObjCTypes:"v@i"]; } return [super methodSignatureForSelector:aSelector]; } - (void)forwardInvocation:(NSInvocation *)anInvocation { // Forward unknown messages to other objects [anInvocation invokeWithTarget:[HTDog new]]; // Change the content of the unknown message (such as method name, method parameter) and forward it to other objects /* anInvocation.selector = @selector(sleep); anInvocation.target = [HTDog new]; int age; [anInvocation getArgument:&age atIndex:2]; // Parameter order: target, selector, other arguments [anInvocation setArgument:&age atIndex:2]; // The number of parameters is determined by the signature of the method returned by the previous method. Pay attention to the problem of array overrun [anInvocation invoke]; int ret; [anInvocation getReturnValue:&age]; // Get return value */ // Define any logic, such as printing only one sentence /* NSLog(@"Study hard); */ } @end
-[HTDog eat]
Source code analysis
// objc-runtime-new.mm(objc4) IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) { ...... // A kind of If the implementation of the method is not found in the "message sending" phase, and it is not solved through "dynamic method parsing" // A kind of Enter the stage of "message forwarding" imp = (IMP)_objc_msgForward_impcache; // Enter assembler cache_fill(cls, sel, imp, inst); // Cache method ...... }
// objc-msg-arm64.s(objc4) STATIC_ENTRY __objc_msgForward_impcache b __objc_msgForward END_ENTRY __objc_msgForward_impcache ENTRY __objc_msgForward adrp x17, __objc_forward_handler@PAGE // A kind of Run C function objc forward handler ldr x17, [x17, __objc_forward_handler@PAGEOFF] br x17 END_ENTRY __objc_msgForward
// objc-runtime.mm(objc4) // Default forward handler halts the process. __attribute__((noreturn)) void objc_defaultForwardHandler(id self, SEL sel) { _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p " "(no message forward handler is installed)", class_isMetaClass(object_getClass(self)) ? '+' : '-', object_getClassName(self), sel_getName(sel), self); } void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
You can see that objc forward handler is a function pointer to objc defaultforwardhandler(), which just prints information. As apple has not opened this source, we can no longer explore the detailed execution logic of "message forwarding".
We know that if we call a method that is not implemented, and there is no "dynamic method parsing" and "message forwarding" processing, it will report the classic crash: unrecognized selector send to instance / class. Let's look at the function call stack of crash printing information. As follows, the system you can see calls a function called forwarding.
This function is in the core foundation framework. Apple has not yet opened the source of this function. We can break into the assembly implementation of this function.
The following is the pseudo code implementation of C language found on the Internet.
// Pseudo code int __forwarding__(void *frameStackPointer, int isStret) { id receiver = *(id *)frameStackPointer; SEL sel = *(SEL *)(frameStackPointer + 8); const char *selName = sel_getName(sel); Class receiverClass = object_getClass(receiver); // A kind of A kind of A kind of A kind of A kind of Call forwardingTargetForSelector: if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) { id forwardingTarget = [receiver forwardingTargetForSelector:sel]; // A kind of Determine whether the method returns an object and the object! = receiver if (forwardingTarget && forwardingTarget != receiver) { if (isStret == 1) { int ret; objc_msgSend_stret(&ret,forwardingTarget, sel, ...); return ret; } //A kind of - objc uumsgsend (return value, sel,...); return objc_msgSend(forwardingTarget, sel, ...); } } // Zombie object const char *className = class_getName(receiverClass); const char *zombiePrefix = "_NSZombie_"; size_t prefixLen = strlen(zombiePrefix); // 0xa if (strncmp(className, zombiePrefix, prefixLen) == 0) { CFLog(kCFLogLevelError, @"*** -[%s %s]: message sent to deallocated instance %p", className + prefixLen, selName, receiver); <breakpoint-interrupt> } // A kind of A kind of A kind of A kind of A kind of Call methodSignatureForSelector to get method signature, and then call forwardInvocation if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) { // A kind of Call methodSignatureForSelector to get method signature NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel]; // A kind of Determine whether the return value is nil if (methodSignature) { BOOL signatureIsStret = [methodSignature _frameDescriptor]->returnArgInfo.flags.isStruct; if (signatureIsStret != isStret) { CFLog(kCFLogLevelWarning , @"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'. Signature thinks it does%s return a struct, and compiler thinks it does%s.", selName, signatureIsStret ? "" : not, isStret ? "" : not); } if (class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) { // A kind of Create an NSInvocation object based on the method signature NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer]; // A kind of Call forwardInvocation [receiver forwardInvocation:invocation]; void *returnValue = NULL; [invocation getReturnValue:&value]; return returnValue; } else { CFLog(kCFLogLevelWarning , @"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message", receiver, className); return 0; } } } SEL *registeredSel = sel_getUid(selName); // Whether the selector has been registered in the Runtime if (sel != registeredSel) { CFLog(kCFLogLevelWarning , @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort", sel, selName, registeredSel); } // A kind of A kind of A kind of A kind of A kind of Call dosnotrecognizeselector else if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) { [receiver doesNotRecognizeSelector:sel]; } else { CFLog(kCFLogLevelWarning , @"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort", receiver, className); } // The point of no return. kill(getpid(), 9); }
summary
At this point, the objc UU msgsend method call process is finished. Let's make a small summary.