Learning about messaging mechanisms

EffectiveOC2.0: effectiveOC2.0 (two objects, messages, runtime)

When you call a method on an object, the term is passing a message. The message has a name and a selector (method), can take arguments, and may have a return value.

In many languages, such as C, calling a method is simply jumping to a point in memory and starting to execute a piece of code. There is no dynamic feature because this is determined at compile time.

In Objective-C, the [object foo] syntax does not immediately execute the code for the method foo. It sends a message called foo to object at runtime. This message may be handled by the object, forwarded to another object, or ignored as if it was not received. Multiple different messages can also correspond to the same method implementation. These are all determined while the program is running.

Learning the messaging mechanism is all about understanding how an OC invokes methods.

id returnValue = [someObject messageName:parameter];
Copy the code

Such a code is processed by the compiler into

 id returnValue = objc_msgSend(someObject, @selectro(messageName:), parameter);
Copy the code

Selector for SEL

OC generates a unique ID that identifies the method based on the method name (including the argument sequence). This ID is SEL ==. Note that as long as the method names (including the argument sequences) are the same, then their ids are the same. So whether it’s a parent class or a child class with the same name then it’s going to have the same ID.

	SEL sell1 = @selector(eat:);
    NSLog(@"sell1:%p", sell1);
    SEL sell2 = @selector(eat);
    NSLog(@"sell2:%p", sell2);
    //sell1:0x100000f63
//sell2:0x100000f68
Copy the code

Notice that at sign selector translates the name of the method into the name of the SEL method, and it only cares about the name of the method and the number of arguments, it doesn’t care about the return value and the type of the arguments

The process of generating SEL is fixed because it is just an ID indicating the method, and the SEL value is fixed no matter which class you write the dayin method in

== A table of SEL is maintained in Runtime. This table stores SEL by class, as long as the same SEL is treated as one and stored in the table. When the project loads, all the methods are loaded into the table, and dynamically generated methods are also loaded into the table. = =

So different classes can have the same method, and instance objects of different classes performing the same selector will look for the IMP of their own class based on the SEL in their method list.

IMP is essentially a pointer to a function that contains the id of the object that received the message, the SEL that called the method, and some method arguments, and returns an ID. So we can get SEL to its corresponding IMP, and after we get the function pointer, that means we get the code entry to execute the method, so we can use the function pointer just like a normal C function call.

The execution process of objc_msgSend()

  1. Message sending phase: Responsible for finding methods from the cache list and method list of the class and its parent class
  2. Dynamic parsing phase: If the method is not found in the message sending phase, it enters the dynamic parsing phase, which is responsible for dynamically adding the method implementation
  3. Message forwarding phase: If the dynamic parsing method is also not implemented, the message forwarding phase takes place, forwarding the message to a recipient who can process the message for processing

Message sending phase

	ENTRY _objc_msgSend // Enter message forwarding
	UNWIND _objc_msgSend, NoFrame
// P0 register, message receiver
🐴	cmp	p0, #0			// nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS🐴 b.l e LNilOrTagged// B is a jump, le is less than or equal to, that is, p0 is less than or equal to 0, jump to LNilOrTagged
#else
	b.eq	LReturnZero
#endif
	ldr	p13, [x0]		// p13 = isa
	GetClassFromIsa_p16 p13		// p16 = class
LGetIsaDone:
🐴	CacheLookup NORMAL	// Cache lookup

#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:// If the receiver is nil, jump here🐴 b.query ReturnZero If the message receiver is empty, exit this function directly// tagged
	adrp	x10, _objc_debug_taggedpointer_classes@PAGE
	add	x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
	ubfx	x11, x0, #60#,4
	ldr	x16, [x10, x11, LSL #3]
	adrp	x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
	add	x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
	cmp	x10, x16
	b.ne	LGetIsaDone

	// ext tagged
	adrp	x10, _objc_debug_taggedpointer_ext_classes@PAGE
	add	x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
	ubfx	x11, x0, #52#,8
	ldr	x16, [x10, x11, LSL #3]
	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_msgSend
	/ / end
Copy the code
  1. First of all, from thecmp p0,#0So to start off, p0 is the register, and this is the message receiver.b.le LNilOrTaggedB is for jump to Le is if p zero is less than or equal to zero, which basically means if p zero is less than or equal to zero, jump toLNilOrTagged, the implementation ofb.eq LReturnZeroI just quit this function
  2. If the message receiver is not nil, the assembly continues to run toCacheLookup NORMALTo see the implementation
/ /
.macro CacheLookup // This is a macro definition
	// p1 = SEL, p16 = isa
	ldp	p10, p11, [x16, #CACHE]	// p10 = buckets, p11 = occupied|mask
#if! __LP64__
	and	w11, w11, 0xffff	// p11 = mask
#endif
	and	w12, w1, w11		// x12 = _cmd & mask
	add	p12, p10, p12, LSL #(1+PTRSHIFT)
		             // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

	ldp	p17, p9, [x12]		// {imp, sel} = *bucket
1:	cmp	p9, p1			// if (bucket->sel ! = _cmd)
	b.ne	2f			// scan more
	CacheHit $0			// call or return imp
	
2:	// not hit: p12 = not-hit bucket
	CheckMiss $0			// miss if bucket->sel == 0
	cmp	p12, p10		// wrap if bucket == buckets
	b.eq	3f
	ldp	p17, p9, [x12, #-BUCKET_SIZE]!	// {imp, sel} = *--bucket
	b	1b			// loop

3:	// wrap: p12 = first bucket, w11 = mask
	add	p12, p12, w11, UXTW #(1+PTRSHIFT)
		                        // p12 = buckets + (mask << 1+PTRSHIFT)

	// Clone scanning loop to miss instead of hang when cache is corrupt.
	// The slow path may detect any corruption and halt later.

	ldp	p17, p9, [x12]		// {imp, sel} = *bucket
1:	cmp	p9, p1			// if (bucket->sel ! = _cmd)
	b.ne	2f			// scan more
🐴	CacheHit $0			// Cache hit, cache found the corresponding method and its implementation -----------------
	
	
2:	// not hit: p12 = not-hit bucket
🐴	CheckMiss $0			// No corresponding method was found in the cache
	cmp	p12, p10		// wrap if bucket == buckets
	b.eq	3f
	ldp	p17, p9, [x12, #-BUCKET_SIZE]!	// {imp, sel} = *--bucket
	b	1b			// loop

3:	// double wrap
	JumpMiss $0
	
.endmacro
Copy the code

I found a method in the cache so I’m going to call it, let’s see what happens if I don’t find a method in the cache and if I don’t find a method I’m going to do a CheckMiss, so let’s look at that, okay

.macro CheckMiss
	// miss if bucket->sel == 0
.if $0 == GETIMP
	cbz	p9, LGetImpMiss
.elseif $0== NORMAL 🐴 CBZ p9, __objc_msgsend_cached// If no method is found in the cache, this method is executed primarily
.elseif $0 == LOOKUP
	cbz	p9, __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
Copy the code

So let’s look at __objc_msgsend_cached

	STATIC_ENTRY __objc_msgSend_uncached
	UNWIND __objc_msgSend_uncached, FrameWithNoSaves

	// THIS IS NOT A CALLABLE C FUNCTION
	// Out-of-band p16 is the class to search
	
🐴	MethodTableLookup // Go to the method list to find the method
	TailCallFunctionPointer x17

	END_ENTRY __objc_msgSend_uncached
Copy the code

We know roughly by the literal name MethodTableLookup that we’re looking for a method from a method list. Let’s look at the structure again

.macro MethodTableLookup
	
	// push frame
	SignLR
	stp	fp, lr, [sp, #- 16]!
	mov	fp, sp

	// save parameter registers: x0.. x8, q0.. q7
	sub	sp, sp, #(10*8 + 8*16)
	stp	q0, q1, [sp, #(0*16)]
	stp	q2, q3, [sp, #(2*16)]
	stp	q4, q5, [sp, #(4*16)]
	stp	q6, q7, [sp, #(6*16)]
	stp	x0, x1, [sp, #(8*16+0*8)]
	stp	x2, x3, [sp, #(8*16+2*8)]
	stp	x4, x5, [sp, #(8*16+4*8)]
	stp	x6, x7, [sp, #(8*16+6*8)]
	str	x8,     [sp, #(8*16+8*8)]

	// receiver and selector already in x0 and x1Mov x2, x16 🐴 __class_lookupMethodAndLoadCache3 bl// Use this method to find the cache and the list of methods

	// IMP in x0
	mov	x17, x0
	
	// restore registers and return
	ldp	q0, q1, [sp, #(0*16)]
	ldp	q2, q3, [sp, #(2*16)]
	ldp	q4, q5, [sp, #(4*16)]
	ldp	q6, q7, [sp, #(6*16)]
	ldp	x0, x1, [sp, #(8*16+0*8)]
	ldp	x2, x3, [sp, #(8*16+2*8)]
	ldp	x4, x5, [sp, #(8*16+4*8)]
	ldp	x6, x7, [sp, #(8*16+6*8)]
	ldr	x8,     [sp, #(8*16+8*8)]

	mov	sp, fp
	ldp	fp, lr, [sp], #16
	AuthenticateLR

.endmacro
Copy the code

Because the assembly function is larger than that of c + + an underscore see _class_lookupMethodAndLoadCache3 implementation

/*********************************************************************** * _class_lookupMethodAndLoadCache. * Method lookup for dispatchers ONLY. OTHER CODE SHOULD USE lookUpImp(). * This lookup avoids optimistic cache scan because the dispatcher * already tried that. **********************************************************************/
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/.NO/*cache*/.YES/*resolver*/);
}
Copy the code

The main thing is to implement the lookUpImpOrForward() method, and then look it up

What we need to understand when we learn this code is what is a method cache

== Apple believes that if a method is called, there is a greater chance that the method will be called again. Since we maintain a cache list directly, we load the called method into the cache list. When we call the method again, we first look in the cache list, and if we can’t find the method, we look in the method list. This avoids having to query the method list every time a method is called, greatly improving the speed ==

Go on do

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
🐴    if (cache) { // Since we did a previous cache lookup, we won't enter here
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

    runtimeLock.lock();
    checkIsKnownClass(cls);

    if(! cls->isRealized()) { realizeClass(cls); }if(initialize && ! cls->isInitialized()) { runtimeLock.unlock(); _class_initialize (_class_getNonMetaClass(cls, inst)); runtimeLock.lock(); } retry: runtimeLock.assertLocked();// Try this class's cache.
// Check the cache again to see if there is any concern that the code is dynamically adding methods at run time
🐴    imp = cache_getImp(cls, sel);
    if (imp) goto done;
   
   // Try this class's method lists.
🐴   // If it is a class object, the following code block is found from the class method list{🐴🐴🐴 Method meth = getMethodNoSuper_nolock(CLS, sel);// Find the method
        if (meth) {
        // Cache the method in the class object cache list and return the IMP of the method
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            gotodone; }}// Try superclass caches and method lists.
🐴    // This is a block of code that looks up from the class object's parent class along the inheritance chain
    {
        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.
            // Check the cache again to prevent code from dynamically adding methods while running
🐴            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.
    
            // Find the 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; }}}... The omissions relate to dynamic method parsing and message forwardingCopy the code

How do I find methods in class objects, primarily in getMethodNoSuper_nolock()

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)
    {
🐴        method_t *m = search_method_list(*mlists, sel);// Use this method to find the method
        if (m) return m;
    }
    return nil;
}
Copy the code

Dynamic analytical phase

Dynamic parsing phase process

  • Dynamic method resolution is initiated when no method is found in the cache or method list of its own class object, and no method is found in the cache or method list of its parent class object
  • Let’s look again at the dynamic analysis of the omission
// No implementation found. Try method resolver once.
	// If no method is found in the class object and the parent object
	// Let's move to dynamic method parsing
    if(resolver && ! triedResolver) {// The triedResolver is used to determine if dynamic method resolution has been performed before. If not, the triedResolver is used to determine if dynamic method resolution has been performed. If so, the triedResolver is skippedruntimeLock.unlock(); 🐴 _class_resolveMethod (CLS, sel, inst);// Dynamic method analytic function
        runtimeLock.lock();
        // 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;// Set this flag to YES after dynamic method parsing
        goto retry;// The whole process of sending the message before, that is, after the method is parsed, we have to go back to the front and look up the class object cache and the method list. If dynamic method resolution adds a method implementation, then it will be found. If not, then the method implementation will not be found, and then it will not go into dynamic method resolution, and it will go straight to the next step, message forwarding
    }
Copy the code

How does _class_resolveMethod implement dynamic method parsing

void _class_resolveMethod(Class cls, SEL sel, id inst)
{
	// Check whether it is not a metaclass object
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]🐴 _class_resolveInstanceMethod (CLS, sel, inst); }// If it is not a class object, it must be a metaclass object
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]🐴 _class_resolveClassMethod (CLS, sel, inst);if(! lookUpImpOrNil(cls, sel, inst,NO/*initialize*/.YES/*cache*/.NO/*resolver*/) {🐴 _class_resolveInstanceMethod(CLS, sel, inst); }}}Copy the code

If it is a class object, then it calls the instance method of the class. If it is a metaclass object, then it calls the resolveInstanceMethod method. If it is a metaclass object, then it calls the resolveClassMethod of the class.

ResolveClassMethod: The default value is NO. If you want to add a method implementation to this function, you need to use class_addMethod

class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char* _Nullable types) @cls: add a method to an object @name: SEL type, add a method to a method name @imp: IMP type, which method implementation to add to the given method name @types: a string representing the return value and parameter typesCopy the code

Dynamic analytical testing

Implement a class that declares a method in a.h file, but does not implement the method in a.m file

If we call this method from the outside we crash the program

It’s easy to see why


  • In the first step of finding the method, there is no implementation of this method in the class object of its own class or in the class object of its parent class
  • So moving to dynamic method analysis, dynamic method analysis we didn’t do anything,
  • So we go to step three, we go to message forwarding, we don’t do anything about message forwarding, and we crash, right

  • Dynamic method analysis
    • When the method lookup in the first step fails, dynamic method resolution is implemented in the resolveInstanceMethod method when the object method is called
    • When a class method is called, dynamic method resolution is implemented in resolveClassMethod
    • With dynamic method parsing and Runtime, we can add a method implementation to a method that is not implemented.
#import "Person.h"
#import <objc/message.h>
@implementation Person

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(eat)) {
        Method method  = class_getInstanceMethod(self.@selector(test2));
        class_addMethod(self, sel, method_getImplementation(method), "123");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

- (void)test2 {
    NSLog(@" Dynamic method parsing");
}

@end
Copy the code

Message forwarding phase

Message forwarding process

	// If no method is found in the class object and the parent object
	// Let's move on to dynamic method parsing
 if(resolver && ! triedResolver) {// The triedResolver is used to determine if dynamic method resolution has been performed before. If not, the triedResolver is used to determine if dynamic method resolution has been performed. If so, the triedResolver is skippedruntimeLock.unlock(); 🐴 _class_resolveMethod (CLS, sel, inst);// Dynamic method analytic function
        runtimeLock.lock();
        // 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; // Set this flag to YES after dynamic method parsing
  🐴     goto retry;// Retry is the previous process of sending the message
    }
    	
  🐴    // If dynamic method parsing fails, message forwarding is entered🐴 imp = (imp) _objc_msgForward_impcache;// From this step into message forwarding
    cache_fill(cls, sel, imp, inst);
// If the message forwarding fails, the program crashesDone: 🐴 runtimeLock. Unlock ();Copy the code

So if this class doesn’t have the ability to process the message, it forwards it to another class and lets another class process it.

Take a look at the implementation of the function __objc_msgForward_impcache that forwards messages

STATIC_ENTRY __objc_msgForward_impcache
	// Method cache version

	// THIS IS NOT A CALLABLE C FUNCTION
	// Out-of-band condition register is NE for stret, EQ otherwise.

	jne	__objc_msgForward_stret
	jmp	__objc_msgForward

	END_ENTRY __objc_msgForward_impcache
	
	
	ENTRY __objc_msgForward
	// Non-stret version

	movq	__objc_forward_handler(%rip), %r11
	jmp	*%r11

	END_ENTRY __objc_msgForward
Copy the code

Unfortunately, __objc_msgForward_handler is not open source

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); 

    / / call forwardingTargetForSelector:
    if (class_respondsToSelector(receiverClass,@selector(forwardingTargetForSelector:))) { 
      / / first call the message receiver forwardingTargetForSelector forward method to get the message object
 🐴    id forwardingTarget = [receiver forwardingTargetForSelector:sel]; 
        if(forwardingTarget && forwarding ! = receiver) {if (isStret == 1) { 
                    intret; objc_msgSend_stret(&ret,forwardingTarget, sel, ...) ;return ret; 
            } 
          	// Then send the message directly to the message forwarding object
            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> 
} 

/ / if forwardingTargetForSelector does not implement or return a value of 0 will continue to perform
  
/ / call methodSignatureForSelector call forwardInvocation again after obtain the method signature
🐴if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) { 
  / / if methodSignatureForSelector the return value is nil
  🐴NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel]; 
    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); 
} 

   // And implement the forwardInvocation
🐴if (class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) { 
    NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer]; 

    [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 is already registered with 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); 
}
  
🐴// If neither of the above methods is implemented, then it will crash
// 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

Message forwarding test

If the implementation of the method is not found in the messaging phase and nothing is done in the dynamic method parsing and message forwarding phases, then it crashes.

After the message is sent in the first phase, the dynamic message is parsed in the second phase. If the dynamic message is not sent in the second phase, the dynamic message is forwarded in the third phase.

== Message forwarding ==

  • First depend on- (id)forwardingTargetForSelector:(SEL)aSelectorThis method, if it returns a message forwarding object directly, forwards the message directly to the message forwarding object via objc_msgSend().
  • If it’s not implemented or it’s nil, it executes-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelectorThis function and-(void)forwardInvocation:(NSInvocation *)anInvocationThis function
// There is no processing in the second phase, the dynamic method parsing phase
/ / in - (id) forwardingTargetForSelector: (SEL) aSelector doesn't do this function
/ / code continue to - (NSMethodSignature *) methodSignatureForSelector (SEL) aSelector this function
// In this function we need to return a method signature:
// Method signature: return value type, parameter type
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if(aSelector == @selector(testAge:)){
        return [NSMethodSignature signatureWithObjCTypes:"v20@0:8i16"];
    }
    return [super methodSignatureForSelector:aSelector];
}
Copy the code

The corresponding relation in “v@:I” is v: void @: eat: : sel I: NSInteger. To assist the runtime system, the compiler encodes the return value of each method and the parameter types and method selectors as strings. The encoding scheme used is useful in other cases as well, so it is public and can be used with the @encode() compiler directive. When given a type parameter, returns an encoded type string. The type can be a basic type such as int, a pointer, a structure, or a union token, or the class name of any type, and in fact, can be used as an argument to the C sizeof() operator. This mechanism is also designed to improve Runtime efficiency.

Code translation table:

Why do we only need to return the return value type and the parameter type here?

  • Person method call- (void)testAge:(int)ageTo do this, we need to know the method caller, the method name, and the method parameters.
  • Whereas in Person.m we certainly know that the method caller is a Person object and that the method name is “testAge:”, so what we don’t know now are the method parameters
  • The method signature then represents the method parameters, including the return value and the parameters, so that the method caller, the method name, and the method parameters are known.

Let’s look at the last function – (void)for invocation :(NSInvocation *)anInvocation

///NSInvocation encapsulates a method invocation, including the method invocation, method name, and method parameters
// anInvocation. Target Method invocation
// anInvocation. Selector name
// [anInvocation getArgument:NULL atIndex:0];
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@ % @ % @ "", anInvocation.target, NSStringFromSelector(anInvocation.selector));
    int age;
    [anInvocation getArgument:&age atIndex:2];
    NSLog(@"%d", age);
    // This line of code changes the method caller to the student object
    [anInvocation invokeWithTarget:[[Student alloc] init]];
}
Copy the code

In this method there is an NSInvocation parameter from which we can get the person object calling – (void)testAge:(int)age method and the method caller, the method name, and the method parameters. We can then achieve message forwarding by changing the method caller, in this case to a Student object. This completes the forwarding of the message to the Student object.

conclusion

We need to understand that the essence of the OC method call is message sending and message sending is the lookup process of SEL-IMP

If a function is not found, OC provides three ways to remedy it

  1. Calling resolveInstanceMethod or resolveClassMethod gives an opportunity to add a method function to an unimplemented method
  2. Call forwardingTargetForSelector let other objects to perform this function
  3. ForwardInvocation Flexibly executes the target function in another form (e.g. change the message in some way before it is triggered, e.g. append another parameter, change selector, etc.)

If not, throw an exception

Let’s use a flow chart to understand the process of message passing and message forwarding

The same message forwarding also involves a knowledge point ——- multiple inheritance multiple inheritance can allow a child to derive from more than one parent, and OC does not support multiple inheritance, but we can indirectly through the protocol, classification, message forwarding to achieveImplementation and difference of iOS multiple inheritanceThe author uses the demo to demonstrate these three situations very well to achieve multiple inheritance whou a total screenshot