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 resolution
Entry:
If (slowpath(behavior & LOOKUP_RESOLVER)) {behavior ^= LOOKUP_RESOLVER; return resolveMethod_locked(inst, sel, cls, behavior); }Copy the code
1.2 resolveMethod_locked
Function 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
- judge
cls
Whether 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 first
resolveClassMethod
, execute oncelookUpImpOrNilTryCache
Perform quick search and slow search to check whether it can be foundimp
If no query is displayed, run the commandresolveInstanceMethod
1.3resolveInstanceMethod
Function 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
+resolveInstanceMethod
Method, forNSObject
And subclasses, with default implementations+resolveInstanceMethod
, the main function here is to add this method to the metaclass cache, for nonNSObject
And its subcategories (e.g.NSProxy
Subclass) if there is no implementation. - Message call
+resolveInstanceMethod
, if the dynamic method resolution givessel
providesimp
Will,sel
withimp
Store the list of methods together. lookUpImpOrNilTryCache
If there is an implementation dynamic method resolution, execute this function to cache the method tocls
thecache
In the
1.3 resolveClassMethod
Function 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
+resolveClassMethod
Method, forNSObject
And subclasses, with default implementations+resolveClassMethod
, the main function here is to add this method to the metaclass cache, for nonNSObject
And its subcategories (e.g.NSProxy
Subclass) if there is no implementation. - Message call
+resolveClassMethod
, if the dynamic method resolution givessel
providesimp
Will,sel
withimp
Store the list of methods together. lookUpImpOrNilTryCache
If there is an implementation dynamic method resolution, execute this function to cache the method tocls
thecache
In the
1.4lookUpImpOrNilTryCache
Function 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
behavior
The default value is 0,resolveInstanceMethod
andresolveClassMethod
All functions use default values0 | LOOKUP_NIL
=4
, so whenlookUpImpOrForward
Imp is queried asforward_imp
When they returnednil
1.5lookUpImpOrForwardTryCache
Function 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
behavior
The default value is 0,resolveMethod_locked
The argument passed to the function is3 and 11
^
2
After the value of the1
or9
&LOOKUP_NIL
for0
, so whenlookUpImpOrForward
Imp is queried asforward_imp
Will 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_getImp
Look for imp in the cache, if foundimp
To jump todone
process- 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
+resolveInstanceMethod
Method to add the obtained IMP to the list of methodsresolveInstanceMethod
A function calllookUpImpOrNilTryCache
When will haveeatTrepang
Method added to cache,resolveMethod_locked
calllookUpImpOrForwardTryCache
, returns the retrieved cacheimp
, returnimp
Method search ended, crash resolved - Because the method has been added to the cache and method list, it is called twice
eatTrepang
Method, 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
+resolveInstanceMethod
This 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 metaclass
NSObject(Meta)
Find the root class in the parent of the root metaclassNSObject
) Look up, soXQPerson
Class implements+resolveInstanceMethod
Method 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_impcache
Jump straight__objc_msgForward
- get
__objc_forward_handler
perform
_objc_forward_handler (objc-runtime.mm) ¶
! __OBJC2__
Can be ignored._objc_forward_handler
The default value isobjc_defaultForwardHandler
.objc_setForwardHandler
Function of the_objc_forward_handler
For 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
-methodSignatureForSelector
and+methodSignatureForSelector
Returns the method signatures for instance methods and class methods, respectively-forwardInvocation
and+forwardInvocation
Handles 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
-methodSignatureForSelector
Method returns a method signature after the-forwardInvocation
Method 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