preface

If a message is sent to an object, the cache will be searched first. If the cache cannot be found, the process will enter the slow search process. If the slow search process still fails to find imp, the process will enter the dynamic method resolution process. Start exploring dynamic method resolution and message forwarding today.

If the parent cache returns a forward_IMP or the parent does not find the corresponding IMP after all traversal, the following logic will be executed first

I. Dynamic method resolution

1. Dynamic method resolution related source code analysis

1.1Dynamic method resolutionEntry:

If (slowpath(behavior & LOOKUP_RESOLVER)) {behavior ^= LOOKUP_RESOLVER; return resolveMethod_locked(inst, sel, cls, behavior); }Copy the code

1.2 resolveMethod_lockedFunction analysis

static NEVER_INLINE IMP resolveMethod_locked(id inst, SEL sel, Class cls, int behavior) { runtimeLock.assertLocked(); ASSERT(cls->isRealized()); runtimeLock.unlock(); // check if it is a metaclass, if it is not a metaclass, it is an instance method if (! cls->isMetaClass()) { // try [cls resolveInstanceMethod:sel] resolveInstanceMethod(inst, sel, cls); } else {/ / try [nonMetaClass resolveClassMethod: sel] / / and/CLS resolveInstanceMethod: sel / / is executive resolveClassMethod metaclass first  resolveClassMethod(inst, sel, cls); ResolveInstanceMethod if (! lookUpImpOrNilTryCache(inst, sel, cls)) { resolveInstanceMethod(inst, sel, cls); // chances are that calling the resolver have populated the cache // so attempt using it // Try to use the return lookUpImpOrForwardTryCache (inst, sel, CLS, behaviors); }Copy the code
  • judgeclsWhether metaclassThe metaclass. If it is not a metaclass, it means it is looking for instance methodsresolveInstanceMethod
  • If it is a metaclass, it means to look for the class method, run firstresolveClassMethod, execute oncelookUpImpOrNilTryCachePerform quick search and slow search to check whether it can be foundimpIf no query is displayed, run the commandresolveInstanceMethod

1.3resolveInstanceMethodFunction analysis:

static void resolveInstanceMethod(id inst, SEL sel, Class cls) { runtimeLock.assertUnlocked(); ASSERT(cls->isRealized()); SEL resolve_sel = @selector(resolveInstanceMethod:); // Find imp for resolve_sel in the metaclass. NSObject implements the +resolveInstanceMethod method on NSObject and its subclasses by default. The main purpose here is to add the +resolveInstanceMethod method to the metaclass cache. NSProxy subclass) if not found, return if (! lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) { // Resolver not implemented. return; } resolveInstanceMethod BOOL (* MSG)(Class, SEL, SEL) = (typeof(MSG))objc_msgSend; bool resolved = msg(cls, resolve_sel, sel); // Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveInstanceMethod adds to self a.k.a. IMP IMP = lookUpImpOrNilTryCache(inST, SEL, CLS); if (resolved && PrintResolving) { if (imp) { _objc_inform("RESOLVE: method %c[%s %s] " "dynamically resolved to %p", cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel), imp); } else { // Method resolver didn't add anything? _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES" ", but no new implementation of %c[%s %s] was found", cls->nameForLogging(), sel_getName(sel), cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel)); }}}Copy the code
  • Determine if there are any in the metaclass+resolveInstanceMethodMethod, forNSObjectAnd subclasses, with default implementations+resolveInstanceMethod, the main function here is to add this method to the metaclass cache, for nonNSObjectAnd its subcategories (e.g.NSProxySubclass) if there is no implementation.
  • Message call+resolveInstanceMethod, if the dynamic method resolution givesselprovidesimpWill,selwithimpStore the list of methods together.
  • lookUpImpOrNilTryCacheIf there is an implementation dynamic method resolution, execute this function to cache the method toclsthecacheIn the

1.3 resolveClassMethodFunction analysis

static void resolveClassMethod(id inst, SEL sel, Class cls) { runtimeLock.assertUnlocked(); ASSERT(cls->isRealized()); ASSERT(cls->isMetaClass()); // find the imp corresponding to @selector(resolveClassMethod:) in the metaclass. NSObject implements the +resolveClassMethod method on NSObject and its subclasses by default. The main purpose here is to add the +resolveClassMethod method to the metaclass cache. NSProxy subclass) if not found, return if (! lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) { // Resolver not implemented. return; } Class nonmeta; { mutex_locker_t lock(runtimeLock); nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst); // +initialize path should have realized nonmeta already if (! nonmeta->isRealized()) { _objc_fatal("nonmeta class %s (%p) unexpectedly not realized", nonmeta->nameForLogging(), nonmeta); } } BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel); // Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveClassMethod adds to self->ISA() a.k.a. cls IMP imp = lookUpImpOrNilTryCache(inst, sel, cls); if (resolved && PrintResolving) { if (imp) { _objc_inform("RESOLVE: method %c[%s %s] " "dynamically resolved to %p", cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel), imp); } else { // Method resolver didn't add anything? _objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES" ", but no new implementation of %c[%s %s] was found", cls->nameForLogging(), sel_getName(sel), cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel)); }}}Copy the code
  • Determine if there are any in the metaclass+resolveClassMethodMethod, forNSObjectAnd subclasses, with default implementations+resolveClassMethod, the main function here is to add this method to the metaclass cache, for nonNSObjectAnd its subcategories (e.g.NSProxySubclass) if there is no implementation.
  • Message call+resolveClassMethod, if the dynamic method resolution givesselprovidesimpWill,selwithimpStore the list of methods together.
  • lookUpImpOrNilTryCacheIf there is an implementation dynamic method resolution, execute this function to cache the method toclsthecacheIn the

1.4lookUpImpOrNilTryCacheFunction analysis

extern IMP lookUpImpOrNilTryCache(id obj, SEL, Class cls, int behavior = 0);

IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
    return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}
Copy the code
  • behaviorThe default value is 0,resolveInstanceMethodandresolveClassMethodAll functions use default values0 | LOOKUP_NIL = 4, so whenlookUpImpOrForwardImp is queried asforward_impWhen they returnednil

1.5lookUpImpOrForwardTryCacheFunction analysis

extern IMP lookUpImpOrForwardTryCache(id obj, SEL, Class cls, int behavior = 0);
IMP lookUpImpOrForwardTryCache(id inst, SEL sel, Class cls, int behavior)
{
    return _lookUpImpTryCache(inst, sel, cls, behavior);
}
Copy the code
  • behaviorThe default value is 0,resolveMethod_lockedThe argument passed to the function is3 and 11 ^ 2After the value of the1or9 & LOOKUP_NILfor0, so whenlookUpImpOrForwardImp is queried asforward_impWill not returnnil

1.7 _lookUpImpTryCache function Parsing

static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior) { runtimeLock.assertUnlocked(); // check if CLS initializes slowPath (! cls->isInitialized())) { // see comment in lookUpImpOrForward return lookUpImpOrForward(inst, sel, cls, behavior); IMP IMP = cache_getImp(CLS, sel); if (imp ! = NULL) goto done; # if CONFIG_USE_PREOPT_CACHES / / determine whether support Shared cache if (fastpath (CLS - > cache. IsConstantOptimizedCache strict (/ * * / true))) { / / in the Shared cache lookup imp imp = cache_getImp (CLS - > cache. PreoptFallbackClass (), sel); If (slowpath(imp == NULL)) {return lookUpImpOrForward(inst, sel, CLS); } done: if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) { return nil; } return imp; }Copy the code
  • cache_getImpLook for imp in the cache, if foundimpTo jump todoneprocess
  • If it is not found in the cache, it determines whether the shared cache is supported
  • Cache does not find IMP, perform slow lookup

2. Dynamic method resolution example:

2.1 Dynamic method resolution cases for instance methods

Declare an XQPerson class as follows:

@interface XQPerson : NSObject
-(void)eatTrepang;
@end

@implementation XQPerson
+(BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"%@ --------- %s",NSStringFromSelector(sel),__func__);

    return [super resolveInstanceMethod:sel];
}
@end

Copy the code

As shown in the code above, an eatTrepang method is declared, not implemented and then called after instantiating XQPerson in the main method

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        XQPerson* person = [[XQPerson alloc]init];
        [person eatTrepang];
    }
    return 0;
}
Copy the code

The result is as follows:

It crashed as we expected, but why did it happen twice? We can analyze that the first execution is triggered during a slow lookup process, so when is the second execution triggered?

By printing the stack information, you can determine that the first call is indeed triggered during a slow lookup

From the print analysis above, the second dynamic method resolution is triggered after the message forwarding ends

2.2 Solutions

The above problem is a declaration of an eatTrepang has not been implemented, just like a person, want to eat sea cucumber, but can not afford to eat, how to do?

Add an eatRice method to the XQPerson class as follows:

@implementation XQPerson
+(BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"%@ --------- %s",NSStringFromSelector(sel),__func__);
    if (sel == @selector(eatTrepang)) {
        IMP imp = class_getMethodImplementation(self, @selector(eatRice));
        Method method = class_getInstanceMethod(self, @selector(eatRice));
        const char *types = method_getTypeEncoding(method);
        class_addMethod(self, sel, imp, types);
    }
    return [super resolveInstanceMethod:sel];
}

-(void)eatRice{
    NSLog(@"%s",__func__);
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        XQPerson* person = [[XQPerson alloc]init];
        
        [person eatTrepang];
        [person eatTrepang];
    }
    return 0;
}
Copy the code

  • in+resolveInstanceMethodMethod to add the obtained IMP to the list of methodsresolveInstanceMethodA function calllookUpImpOrNilTryCacheWhen will haveeatTrepangMethod added to cache,resolveMethod_lockedcalllookUpImpOrForwardTryCache, returns the retrieved cacheimp, returnimpMethod search ended, crash resolved
  • Because the method has been added to the cache and method list, it is called twiceeatTrepangMethod, execute dynamic method resolution only once,
2.2 Case analysis of Class Methods:
2.2.1 Change the above method toClass method, as follows:
@interface XQPerson : NSObject
+(void)eatTrepang;
@end

@implementation XQPerson
+(BOOL)resolveClassMethod:(SEL)sel{
    NSLog(@"%@ --------- %s",NSStringFromSelector(sel),__func__);
    
    return [super resolveClassMethod:sel];
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [XQPerson eatTrepang];
        
    }
    return 0;
}
Copy the code

After +resolveClassMethod is executed twice, the logic of calling +resolveInstanceMethod twice is similar to that of +resolveInstanceMethod

2.2.2 Solution
@implementation XQPerson
+(BOOL)resolveClassMethod:(SEL)sel{
    NSLog(@"%@ --------- %s",NSStringFromSelector(sel),__func__);
    if (sel == @selector(eatTrepang)) {
        IMP imp = class_getMethodImplementation(object_getClass(self), @selector(eatRice));
        Method method = class_getClassMethod(self, @selector(eatRice));
        const char *types = method_getTypeEncoding(method);
        class_addMethod(object_getClass(self), sel, imp, types);
    }
    return [super resolveClassMethod:sel];
}

+(void)eatRice{
    NSLog(@"%s",__func__);
}

@end
Copy the code

  • Analysis process and+resolveInstanceMethodThis is similar, except that the method is added to the list of methods in the metaclass

As has been analyzed above, when the resolveClassMethod function fails to solve the problem, the resolveInstanceMethod function will be called once. Then, does it mean that the class method only needs to implement the +resolveInstanceMethod method? And provide an implementation that solves the problem? Next make the following changes to the code and execute:

@implementation XQPerson
+(BOOL)resolveClassMethod:(SEL)sel{
    NSLog(@"%@ --------- %s",NSStringFromSelector(sel),__func__);
    return [super resolveClassMethod:sel];
}

+(BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"%@ --------- %s",NSStringFromSelector(sel),__func__);
    return [super resolveInstanceMethod:sel];
}

@end
Copy the code

Execution Result:

The +resolveInstanceMethod method is not executed. Why is that?

To review the resolveInstanceMethod function again:

static void resolveInstanceMethod(id inst, SEL sel, Class cls) { runtimeLock.assertUnlocked(); ASSERT(cls->isRealized()); SEL resolve_sel = @selector(resolveInstanceMethod:); if (! lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) { // Resolver not implemented. return; } BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; Bool resolved = MSG (CLS, resolve_sel, sel); // When CLS is a metaclass, send a message to the metaclass. IMP imp = lookUpImpOrNilTryCache(inst, sel, cls); if (resolved && PrintResolving) { if (imp) { _objc_inform("RESOLVE: method %c[%s %s] " "dynamically resolved to %p", cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel), imp); } else { // Method resolver didn't add anything? _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES" ", but no new implementation of %c[%s %s] was found", cls->nameForLogging(), sel_getName(sel), cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel)); }}}Copy the code
  • When a class method is called, the CLS is currently a metaclass and sends a message to the metaclass, first in the metaclass, the root metaclassNSObject(Meta)Find the root class in the parent of the root metaclassNSObject) Look up, soXQPersonClass implements+resolveInstanceMethodMethod does not execute.

It can be inferred from the above analysis that the root class (NSObject) +resolveInstanceMethod method will be executed when XQPerson does not implement the +resolveClassMethod method or when the corresponding IMP is not added after the implementation. Next, add a category to XQPerson:

@interface NSObject(XQ)

@end

@implementation NSObject(XQ)
+(BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"%@ --------- %s",NSStringFromSelector(sel),__func__);
    return NO;
}
@end
Copy the code

As expected, XQPerson’s +resolveClassMethod is called first, followed by NSObject(XQ) ‘s +resolveInstanceMethod method

NSObject’s +resolveInstanceMethod method is implemented, so we can integrate dynamic method resolutions in +resolveInstanceMethod:


@interface XQPerson : NSObject
+(void)eatTrepang;
-(void)eatTrepang;
@end

@implementation XQPerson
+(BOOL)resolveClassMethod:(SEL)sel{
    if (sel == @selector(eatTrepang)) {
        NSLog(@"%@ --------- %s",NSStringFromSelector(sel),__func__);
    }
    return [super resolveClassMethod:sel];
}

@end

@interface NSObject(XQ)

@end


@implementation NSObject(XQ)
+(BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(eatTrepang)) {
        NSLog(@"%@ --------- %s",NSStringFromSelector(sel),__func__);
        IMP imp = imp_implementationWithBlock(^{
            NSLog(@"eatRice");
        });
        const char *types = "v@0:8";
        class_addMethod(self, sel, imp, types);
    }
    return NO;
}
@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [XQPerson eatTrepang];
        XQPerson* person = [[XQPerson alloc]init];
        [person eatTrepang];
    }
    return 0;
}
Copy the code

In the example above, the XQPerson class declares the +(void)eatTrepang and -(void)eatTrepang methods and calls them respectively. The dynamic method resolution is implemented into NSObject(XQ) +resolveInstanceMethod, and the appropriate IMP is added.

Dynamic method resolution flowchart

2. Message forwarding

What does the program do if, after the dynamic method resolution, the appropriate IMP is still not returned

The following code was mentioned when analyzing method slow lookups:

// imp const imp forward_IMP = (imp)_objc_msgForward_impcache;Copy the code

If _objc_msgForward_impcache is specified as imp for message forwarding, search globally for _objc_msgForward_impcache and find the following code in objc-msG-arm64.s:

STATIC_ENTRY __objc_msgForward_impcache

// No stret specialization.
b	__objc_msgForward

END_ENTRY __objc_msgForward_impcache


ENTRY __objc_msgForward

adrp	x17, __objc_forward_handler@PAGE
ldr	p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17

END_ENTRY __objc_msgForward
Copy the code
  • __objc_msgForward_impcacheJump straight__objc_msgForward
  • get__objc_forward_handlerperform

_objc_forward_handler (objc-runtime.mm) ¶

  • ! __OBJC2__Can be ignored.
  • _objc_forward_handlerThe default value isobjc_defaultForwardHandler.
  • objc_setForwardHandlerFunction of the_objc_forward_handlerFor the assignment

The objc_defaultForwardHandler function is a very familiar code: unrecognized selector sent to instance… The first thing we thought was that this might be the function we were looking for, but when we broke the point in this function, it didn’t execute, and when we broke the point in objc_setForwardHandler, Found that the reassignment here is _CF_forwarding_prep_0 of the CoreFoundation framework

At this point, when we pass the breakpoint, no surprise, the program will flash back:

Since CoreFoundation is not completely open source, we can’t find relevant code in it, so how can we explore the process of message forwarding? We can use log-assisted debugging to do this.

Log-assisted debugging

Function call logging is done in the logMessageSend function. The logMessageSend function is searched globally. This function is called in the log_and_fill_cache function, ObjcMsgLogEnabled is true. If you search objcMsgLogEnabled globally, it defaults to false, so we need to assign it to change it.

void instrumentObjcMessageSends(BOOL flag) { bool enable = flag; // Shortcut NOP if (objcMsgLogEnabled == enable) return; // If enabling, flush all method caches so we get some traces if (enable) _objc_flush_caches(Nil); // Sync our log file if (objcMsgLogFD ! = -1) fsync (objcMsgLogFD); objcMsgLogEnabled = enable; }Copy the code

In instrumentObjcMessageSends function assignment, so we only need to log before the assignment to YES, need to be closed when the assignment to NO, as shown below:

extern void instrumentObjcMessageSends(BOOL flag);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        instrumentObjcMessageSends(YES);
        [XQPerson eatTrepang];
        instrumentObjcMessageSends(NO);
    }
    return 0;
}
Copy the code

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

By the above log, we can determine and class methods after resolution method of dynamic execution + + methodSignatureForSelector forwardingTargetForSelector – > in turn

The sample methods are also tested in sequence, with the following code:

extern void instrumentObjcMessageSends(BOOL flag);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        instrumentObjcMessageSends(YES);
        XQPerson* person = [[XQPerson alloc]init];
        [person eatTrepang];
        instrumentObjcMessageSends(NO);
    }
    return 0;
}
Copy the code

The log is as follows:

By the above log, we can determine and instance methods dynamic method in sequence after the resolution – forwardingTargetForSelector – > – methodSignatureForSelector

As you can see, the message forwarding process for instance methods and class methods is similar.

Fast forwarding process

ForwardingTargetForSelector method, we first analysis the search in the source code, can be in NSObject. Mm check to the following results:

Both methods return nil, see the official documentation below:

As you can see from the documentation, you need to return a non-empty and non-self object as the new recipient of the message. If the new object still cannot be processed, it will be looked up in the parent class

Quick forwarding cases:

Make the following changes to the previous code:

@interface MensFootBall : NSObject @end @implementation MensFootBall -(void)eatTrepang{ NSLog(@"%s",__func__); } +(void)eatTrepang{ NSLog(@"%s",__func__); } @end @interface XQPerson : NSObject -(void)eatTrepang; +(void)eatTrepang; @end @implementation XQPerson -(id)forwardingTargetForSelector:(SEL)aSelector { if (aSelector == @selector(eatTrepang)) { return [[MensFootBall alloc]init]; } return [super forwardingTargetForSelector:aSelector]; } + (id)forwardingTargetForSelector:(SEL)sel { if (sel == @selector(eatTrepang)) { return [MensFootBall class]; } return [super forwardingTargetForSelector:sel]; } @end extern void instrumentObjcMessageSends(BOOL flag); int main(int argc, const char * argv[]) { @autoreleasepool { instrumentObjcMessageSends(YES); [XQPerson eatTrepang]; XQPerson* person = [[XQPerson alloc]init]; [person eatTrepang]; instrumentObjcMessageSends(NO); } return 0; } * * * * * * * * * * * * * * * * * * * * * * * * * * * * * the execution result as below * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2022-02-21 18:06:42. 593261 + 0800 forward (38238-631847) +[MensFootBall eatTrepang] 2022-02-21 18:06:42.595062+0800 Message forwarding [38238:631847] -[MensFootBall eatTrepang] Program ended with exit code: 0Copy the code

As can be seen from the print result, both +eatTrepang and -eattrepang that could not be executed before were forwarded to MensFootBall and successfully executed. Ordinary people can’t afford to eat sea cucumber, but China football can!

Slowly forward

If the fast forwarding process is not implemented, or the message recipient specified by fast forwarding is still unable to handle the task, the slow forwarding process enters.

Open the official documentation, search methodSignatureForSelector, the results are as follows:

The document shows that needs and forwardInvocation, methodSignatureForSelector needs to return a method signature, forwardInvocation process the message

Next, make the following changes to XQPerson:

@implementation XQPerson - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { if (aSelector == @selector(eatTrepang)) { NSLog(@"%s ---- %@",__func__,NSStringFromSelector(aSelector)); NSMethodSignature* sign = [NSMethodSignature signatureWithObjCTypes:"v@:"]; return sign; } return [super methodSignatureForSelector:aSelector]; } + (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { if (aSelector == @selector(eatTrepang)) { NSLog(@"%s ---- %@",__func__,NSStringFromSelector(aSelector)); NSMethodSignature* sign = [NSMethodSignature signatureWithObjCTypes:"v@:"]; return sign; } return [super methodSignatureForSelector:aSelector]; } -(void)forwardInvocation:(NSInvocation *)anInvocation { NSLog(@"%@------%@",anInvocation.target,NSStringFromSelector(anInvocation.selector)); if (anInvocation.selector == @selector(eatTrepang)) { [anInvocation invokeWithTarget:[MensFootBall new]]; } } +(void)forwardInvocation:(NSInvocation *)anInvocation { NSLog(@"%@------%@",anInvocation.target,NSStringFromSelector(anInvocation.selector)); if (anInvocation.selector == @selector(eatTrepang)) { [anInvocation invokeWithTarget:MensFootBall.class]; }} * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * printing result * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2022-02-22 10:20:39. 004341 + 0800 forward (8367-191657) + [XQPerson methodSignatureForSelector:] -- eatTrepang 10:20:39 2022-02-22. 006389 + 0800 forward [8367-191657] + [XQPerson ForwardInvocation :]------------XQPerson------eatTrepang 2022-02-22 10:20:39.007095+0800 Message forwarding [8367:191657] +[MensFootBall EatTrepang] 2022-02-22 10:20:39. 008037 + 0800 forward [8367-191657] - [XQPerson methodSignatureForSelector:] - eatTrepang 2022-02-22 10:20:39.009661+0800 Message Forwarding [8367:191657] -[XQPerson forwardInvocation:]------------<XQPerson: 0x10A004200 >------eatTrepang 2022-02-22 10:20:39.010352+0800 Message forwarding [8367:191657] -[MensFootBall eatTrepang]Copy the code
  • -methodSignatureForSelectorand+methodSignatureForSelectorReturns the method signatures for instance methods and class methods, respectively
  • -forwardInvocationand+forwardInvocationHandles the forwarding of class methods separately
  • As a result of the above, the instance and class methods are successfully forwarded, and the flashback is resolved

All messages are forwarded

Each of the above methods requires specifying an object to receive the message for processing, so is there a way to handle all message forwarding?

@interface XQSafe : NSObject @end @implementation XQSafe @end @interface XQPerson : NSObject -(void)eatTrepang; +(void)eatTrepang; @end @implementation XQPerson - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSMethodSignature* sign  = [NSMethodSignature signatureWithObjCTypes:"v@:"]; return sign; } -(void)forwardInvocation:(NSInvocation *)anInvocation { @try { [anInvocation invokeWithTarget:[XQSafe new]]; } @catch (NSException *exception) { NSLog(@"%@ -- -- -- -- -- - % @ exception happened -- -- -- -- -- - ", anInvocation. Target, NSStringFromSelector anInvocation. The selector ()); } @finally { } } @end int main(int argc, const char * argv[]) { @autoreleasepool { XQPerson* person = [[XQPerson alloc]init]; [person eatTrepang]; [person performSelector:@selector(eatRice:) withObject:@(1)]; } return 0; } * * * * * * * * * * * * * * * * * * * * * * * * * * * the execution result * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2022-02-22 11:26:16. 371396 + 0800 forward (13131-276987) -[XQSafe eatTrepang]: Unrecognized selector sent to instance 0x1098a9e60 2022-02-22 11:26:18.839312+0800 Message forward [13131:276987] <XQSafe: 0x1098A9e60 > ------eatTrepang------ An exception occurred 2022-02-22 11:26:18.839508+0800 Message forwarding [13131:276987] -[XQSafe eatRice]: Unrecognized selector sent to instance 0x109D04440 2022-02-22 11:26:19.673050+0800 message forward [13131:276987] <XQSafe: 0x109D04440 > ------eatRice:------ An exception occurred Program ended with exit code: 0Copy the code
  • in-methodSignatureForSelectorMethod returns a method signature after the-forwardInvocationMethod throws an exception to handle all message forwarding without blinking back
  • This approach is useful when you don’t care about execution results and just want to avoid flashbacks