Runtime Series

Data structure Data structure Message mechanism The nature of super The nature of Super

1. Objc_msgSend method invocation process

When a method is called in OC, the compiler calls one of the following functions for messaging, depending on the situation: objc_msgSend, objc_msgSend_stret, objc_msgSendSuper, objc_msgSendSuper_stret. Objc_msgSendSuper is called when the method caller is super, and objc_msgSend_stret or objc_msgSendSuper_stret is called when the data structure is the return value. All other method calls are converted to calls to the objc_msgSend() function.

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

Send a message to the receiver (SEL method name)

  • Parameter 1:receiver
  • Argument 2:SEL
  • Parameters 3, 4, 5… :SELMethod parameters

The execution of objc_msgSend() can be divided into three phases:

  • Message is sent
  • Dynamic method parsing
  • forward

 

2. Send messages

Message Send process

Source code analysis

As mentioned in the previous article, Runtime is a Runtime library written in C, assembly. If you need to call C in the underlying assembly, Apple will add an underscore _ to it, so to see the implementation of objc_msgSend, you need to search for _objc_msgSend (objC-msG-arm64.s (objC4)).

// objc-msg-arm64.s (objc4)
    /* _objc_msgSend implements */
    // ⚠️ Assembler ENTRY format: ENTRY + function name
	ENTRY _objc_msgSend

    // ⚠️ Execute LNilOrTagged if receiver is nil or tagged pointer, otherwise proceed
	cmp	x0, #0			// nil check and tagged pointer check
	b.le	LNilOrTagged   

    // ⚠️ use isa to find class/meta-class
	ldr	x13, [x0]		// x13 = isa
	and	x16, x13, #ISA_MASK	// x16 = class	
LGetIsaDone:
    // ⚠️ Enter the cache for search and pass the parameter NORMAL
    // CacheLookup macro, used to find SEL corresponding method implementation in cache
	CacheLookup NORMAL		// calls imp or objc_msgSend_uncached 

LNilOrTagged:
    // ⚠️ If receiver is nil, execute LReturnZero to end objc_msgSend
	b.eq	LReturnZero		// nil check 
    // ⚠️ If the receiver is tagged pointer, the other is executed. b LGetIsaDone LReturnZero: ret/ / return

    // ⚠️ assembly, the function end format is: ENTRY + function name
	END_ENTRY _objc_msgSend



.macro CacheLookup
    // ⚠️ select buckets from 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
    // ⚠️ perform a CacheHit on a CacheHit
1:	cmp	x9, x1			// if (bucket->sel ! = _cmd)
	b.ne	2f			// scan more
	CacheHit $0			// call or return imp
    // if ⚠️ is not found in cache, CheckMiss is performed
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  // ⚠️CacheLookup is NORMAL
	MESSENGER_END_FAST
	br	x17			// call imp // ⚠️ executes the 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  // ⚠️CacheLookup is NORMAL
	cbz	x9, __objc_msgSend_uncached  // ⚠️ 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
    The implementation of the method was not found in the ⚠️ cache, so go to the method list on the methodTable ookup classSTATIC_ENTRY __objc_msgSend_uncached MethodTableLookup NORMAL END_ENTRY __objc_msgSend_uncached .macro MethodTableLookup  blx __class_lookupMethodAndLoadCache3/ / ⚠ ️ _class_lookupMethodAndLoadCache3 do C function
.endmacro
Copy the code

Instead, an underscore _ should be removed to find the corresponding C function implementation from the function name in assembly.

// objc-Runtime-new.mm (objc4)
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    // ⚠️ note that the parameter is passed, since the method has been searched in the cache by assembly, so it will not be searched 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;  // The triedResolver tag is used for dynamic method resolution

    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()) {// ⚠️ if receiverClass(message receiverClass) has not been implemented, realize
        // 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();
    }

    // ⚠️ If receiverClass needs to be initialized and has not been initialized, initialize it
    // insert the +initialize method here
    _class_initialize(CLS ->superclass); // Call _class_initialize(CLS ->superclass).
    // call callInitialize(CLS) and send an initialize message to CLS (void(*)(Class, SEL))objc_msgSend (CLS, SEL_initialize)
    // So the +initialize method is called the first time the class receives a message
    // Call method: objc_msgSend()
    // Call order: call +initialize of parent class first, then call +initialize of child class.
    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
    }


/ / ⚠ ️ ⚠ ️ ⚠ ️ core
 retry:    
    runtimeLock.assertReading();

    // ⚠️ go to the receiverClass cache to find the method, if found imp directly call
    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // ⚠️ look for the method in the list of methods in the receiverClass class_rw_t. If you find imp, call the method and cache it in the receiverClass cache
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);  // ⚠️ 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);  // ⚠️ cache method
            imp = meth->imp;
            gotodone; }}// ⚠️ searches the parent class's cache and method list level by level. If imp is found, the method is called and cached in the receiverClass cache
    {
        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;
                gotodone; }}}// ⚠️ Enter the dynamic method resolution phase
    // 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;
    }


    // ⚠️ The message forwarding phase is displayed
    // 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;
}
Copy the code

Let’s see how getMethodNoSuper_nolock(CLS, SEL) looks up method implementations from classes

  • If the list of methods is sorted, binary lookup is performed;
  • If the list of methods 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) {// ⚠️ 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)) {/ / ⚠ ️ if the method of the list is sorted, In binary search return findMethodInSortedMethodList (sel, mlist); } else {// ⚠️ Linear search of unsorted method list for (auto& meth: 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; // ⚠️count >>= 1 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; }Copy the code

Log_and_fill_cache (CLS, meth-> IMP, SEL, inst, CLS

/***********************************************************************
* 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
Copy the code

The cache_fill() function implementation was written about in the previous article.

To learn more about the cache lookup process and about cache_t, you can see

2. Dynamic method parsing

Dynamic Method resolution process

If the method implementation is not found in the “message send” phase, a “dynamic method resolution” is performed.

After Dynamic method resolution, the system enters the Message sending process again, which starts from finding the method in the receiverClass cache.

We can override the following methods based on the method type (instance method or class method)

+ (BOOL)resolveInstanceMethod:(SEL)sel; + (BOOL)resolveClassMethod:(SEL)sel;
Copy the code

Call the following functions in a method to dynamically add the implementation of the method

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
Copy the code

The sample code is as follows. We call the HTPerson eat instance method and the class method respectively, but there is no corresponding implementation of these two methods in the htPerson. m file.

// 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;  // There is no corresponding implementation
- (void)sleep;
+ (void)eat;  // There is 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)) {
        
        Method is a pointer to the method_t structure
        Method method = class_getInstanceMethod(self.@selector(sleep));
        /* ** Parameter 1: which class to add ** parameter 2: which method to add ** parameter 3: method implementation address ** parameter 4: method encoding type */
        class_addMethod(self.// The instance method is stored in the class object, so the class object is passed in here
                        sel,
                        method_getImplementation(method),
                        method_getTypeEncoding(method)
                        );
        // Returning YES means there is a dynamically added method implementation
        // From the source, the return value is only used to print information about the result of the parse, and does not affect the result of the dynamic method parse
        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 the metaclass object is passed in here
                        sel,
                        method_getImplementation(method),
                        method_getTypeEncoding(method)
                        );
        return YES;
    }
    return [super resolveClassMethod:sel];
}

@end

Copy the code

-[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: ......// ⚠️ If the method implementation is not found in the "message send" phase, perform a "dynamic method resolution"
    if(resolver && ! triedResolver) { runtimeLock.unlockRead(); _class_resolveMethod(cls, sel, inst);// ⚠️ 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;  // ⚠️ flags triedResolver as YES
        goto retry;  // ⚠️ enter message sending again, starting from the "Go to receiverClass cache to find methods" step
    }

    // ⚠️ The message forwarding phase is displayed. }// objc-class.mm (objc4)
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    ⚠️ determine whether it is a class object or a meta-class object
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        // ⚠️ core function
        _class_resolveInstanceMethod(cls, sel, inst);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        // ⚠️ 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)
{
    // ⚠️ Check whether the method list of the meta-class object of receiverClass contains the SEL_resolveInstanceMethod imp function
    +(BOOL)resolveInstanceMethod:(SEL) SEL method
    // ⚠️ you must find the method implementation here, because NSObject has an implementation
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/.YES/*cache*/.NO/*resolver*/)) 
    {
        // ⚠️ If no, the program is abnormal
        // Resolver not implemented.
        return;
    }

    // ⚠️ If found, send a SEL_resolveInstanceMethod message to the object via objc_msgSend
    // ⚠️ +(BOOL)resolveInstanceMethod (SEL) SEL
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);

    // ⚠️ Below is some printout of the parsing result. }/***********************************************************************
* _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());
    // ⚠️ Check whether the method list of the meta-class object of receiverClass contains the SEL_resolveClassMethod imp function
    +(BOOL)resolveClassMethod:(SEL) SEL method
    // ⚠️ you must find the method implementation here, because NSObject has an implementation
    if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, 
                         NO/*initialize*/.YES/*cache*/.NO/*resolver*/)) 
    {
        // ⚠️ If no, the program is abnormal
        // Resolver not implemented.
        return;
    }
    // ⚠️ If found, send the object a SEL_resolveClassMethod message via objc_msgSend
    // ⚠️ +(BOOL)resolveClassMethod:(SEL) SEL
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(_class_getNonMetaClass(cls, inst),   // This function returns a class object, not a metaclass object
                        SEL_resolveClassMethod, sel);

    // ⚠️ Below is some printout of the parsing result. }Copy the code

3. Forwarding messages

Message forwarding process

If the implementation of the method is not found in the “message sending” stage, and the solution is not solved through the “dynamic method resolution”, the “message forwarding” stage is entered.

The “message forwarding” stage is divided into two steps: Fast forwarding and Normal forwarding. As the name implies, the first step is faster than the second step.

  • Fast forwarding: To forward a message to another OC object (find an alternate receiver), we can override the following method to return one! = receiverObject to accomplish this step;+/- (id)forwardingTargetForSelector:(SEL)sel
  • Normal forwarding: To realize a complete message forwarding process,

If the previous step failed to resolve the unknown message, you can override the following two methods to initiate full message forwarding.

① The first method: We need to return a method signature in this method that is appropriate for the unknown message. (The method signature is a description of the return value Type and parameter Type, and can be encoded as Type Encodings.) Data structure).

(+ / -NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
Copy the code

The Runtime will sign the method and create a NSInvocation object (NSInvocation encapsulates the entire contents of the unknown message, including: Method invocation target, method name Selector, method argument, etc.) and then calls the second method and passes in the NSInvocation object as a parameter.

② The second method: in this method, we can forward the unknown message to other objects; Change the contents of the unknown message (such as method name and method parameters) and then forward the message to other objects; You can even define any logic.

(+ / -void)forwardInvocation:(NSInvocation *)invocation
Copy the code

If we don’t return the method signature in the first method, or if we don’t override the second method, the system will assume that we don’t want to process the message at all, This time is called + / – (void) doesNotRecognizeSelector: (SEL) SEL method and throw the classic crash: unrecognized selector that sent to the instance/class, End the entire objc_msgSend process.

Let’s look at the default implementations of these codes:

// NSObject.mm
+ (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);
}
Copy the code

Example code for Fast Forwarding is as follows:

We called the HTPerson eat instance method, but there is no corresponding implementation of this method in the htperson. m file. There is an implementation of the method with the same name in htdog. m.

// 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;  // There is 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 HTDog instance object
// 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
Copy the code

-[HTDog eat]

The sample code for Normal Forwarding and the output 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 contents of the unknown message (such as method name, method parameters) and forward it to other objects
    /* anInvocation.selector = @selector(sleep); anInvocation.target = [HTDog new]; int age; [anInvocation getArgument:&age atIndex:2]; // Argument sequence: Target, Selector, other arguments [anInvocation Invocation setArgument:&age atIndex:2]; // The number of arguments is determined by the method signature returned by the invocation invocation. int ret; [anInvocation getReturnValue:&age]; // Get the return value */
    
    // Define any logic, such as: print only one sentence
    /* NSLog(@" study hard "); * /
}

@end
Copy the code

-[HTDog eat]

Source code analysis

// objc-Runtime-new.mm (objc4)
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    ......

    // ⚠️ If the method implementation is not found in the "message send" phase and is not resolved through "dynamic method resolution"
    // ⚠️ The message forwarding phase is displayed
    imp = (IMP)_objc_msgForward_impcache;  // Enter the assembly
    cache_fill(cls, sel, imp, inst);       // Cache methods. }Copy the code
// 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// ⚠️ executes the C function _objc_forward_handler
	ldr	x17, [x17, __objc_forward_handler@PAGEOFF]
	br	x17
	END_ENTRY __objc_msgForward
Copy the code
// 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))?'+' : The '-', 
                object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
Copy the code

You can see that _objc_forward_handler is a function pointer to objc_defaultForwardHandler(), which simply prints information. Since Apple doesn’t open source this, we can’t delve further into the detailed execution logic of “message forwarding.”

As we know, calling an unimplemented method without “dynamic method resolution” and “message forwarding” processing would send the classic crash: unrecognized selector sent to instance/class. The system called a function named ___forwarding___.

This function is in the CoreFoundation framework, apple has not open source this function, we can break point into the assembly implementation of this function.

The following C pseudo-code implementation of ___forewarding___ is 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);

    / / ⚠ ️ ⚠ ️ ⚠ forwardingTargetForSelector ️ call:
    if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
        id forwardingTarget = [receiver forwardingTargetForSelector:sel];
        // ⚠️ Determine if the method returns an object and the object! = receiver
        if(forwardingTarget && forwardingTarget ! = receiver) {if (isStret == 1) {
                intret; objc_msgSend_stret(&ret,forwardingTarget, sel, ...) ;return ret;
            }
            ⚠️objc_msgSend(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>
    }

    / / ⚠ ️ ⚠ ️ ⚠ ️ call methodSignatureForSelector call forwardInvocation again after obtain the method signature
    if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
        / / ⚠ methodSignatureForSelector ️ call to obtain the method signature
        NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
        // ⚠️ determines if 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:))) {

                // ⚠️ Create an NSInvocation object based on the method signature
                NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];

                // ⚠️ invoke the 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);

    // Selector is already registered with 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);
    } // ⚠️⚠️⚠️ doesNotRecognizeSelector
    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);
}
Copy the code

 

conclusion

This concludes the objc_msgSend method call flow. So let’s do a quick summary.

Objc_msgSend Execution flow chart