Message forwarding trilogy:
After the above message is sent, if no implementation is found in the current class or parent class, dynamic method resolution is attempted.
Dynamic method parsing
// No implementation found. Try method resolver onceif(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;
}
Copy the code
After _class_resolveMethod: is executed, it jumps to the Retry tag and restarts the process of finding the method implementation without calling _class_resolveMethod: Method, because the triedResolver is used to determine whether the class has been dynamically resolved. TriedResolver = NO for the first time. If dynamic resolver has been resolved once, triedResolver = YES will be set so that dynamic resolver will not be resolved again for the next time.
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
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
The _class_resolveMethod function checks whether it is a meta-class. If it is not a metaclass, the _class_resolveInstanceMethod function checks whether it is a meta-class. Perform _class_resolveClassMethod.
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveInstanceMethod adds to self a.k.a. cls IMP imp = lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/); 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() ? '+' : The '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
assert(cls->isMetaClass());
if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(_class_getNonMetaClass(cls, inst),
SEL_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 = lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/); 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() ? '+' : The '-', cls->nameForLogging(), sel_getName(sel)); }}}Copy the code
In the _class_resolveInstanceMethod and _class_resolveClassMethod methods, to check whether the implementation function has been dynamically inserted into the class at run time. If lookUpImpOrNil is not re-called and the cache is restarted, To determine whether the IMP pointer corresponding to SEL has been added, and refires the objc_msgSend method.
id objc_msgSend(id self, SEL _cmd, ...) {
Class class = object_getClass(self);
IMP imp = class_getMethodImplementation(class, _cmd);
return imp ? imp(self, _cmd, ...) : 0;
}
Copy the code
Any time you mention objc_msgSend, you’ll be told that its pseudocode looks like this or something like this, just to get an IMP and call it.
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
if(! cls || ! sel)return nil;
imp = lookUpImpOrNil(cls, sel, nil, YES/*initialize*/, YES/*cache*/, YES/*resolver*/);
// Translate forwarding function to C-callable external version
if(! imp) {return _objc_msgForward;
}
return imp;
}
Copy the code
LookUpImpOrNil returns _objc_msgForward if it fails to obtain IMP.
IMP lookUpImpOrNil(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
if (imp == _objc_msgForward_impcache) return nil;
else return imp;
}
Copy the code
LookUpImpOrNil determines whether the imp result returned is the same as _objc_msgForward_impcache, or nil, if so, imp.
Back in the lookUpImpOrForward method, if no imp implementation is found, then the method resolver is useless and the message forwarding phase is entered. Before entering this stage, imp becomes _objc_msgForward_impcache and is finally added to the cache.
When a method is not implemented, you can dynamically add unimplemented methods by overriding the resolveInstanceMethod: and resolveClassMethod: methods. The first is to add instance methods and the second is to add class methods. Both methods return a BOOL, and return NO to enter the message forwarding mechanism.
void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], sel, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
Copy the code
objc_msgForward
STATIC_ENTRY __objc_msgForward_impcache
// Method cache version
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band condition register is NE forstret, EQ otherwise. MESSENGER_START nop MESSENGER_END_SLOW jne __objc_msgForward_stret //1. Jump to __objc_msgForward JMP __objc_msgForward END_ENTRY __objc_msgForward_impcache ENTRY __objc_msgForward // non-stret version //2. __objc_forward_handler movq __objc_forward_handler(%rip) %r11 jmp *%r11 END_ENTRY __objc_msgForward ENTRY __objc_msgForward_stret // Struct-return version movq __objc_forward_stret_handler(%rip), %r11 jmp *%r11 END_ENTRY __objc_msgForward_stretCopy the code
The __objc_forward_handler function is called after _objc_msgForward is executed.
__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
When we send an object with an unimplemented method, if its parent class does not have a method, it crashes with an error message like this: Unrecognized selector sent to instance, and then some stack information pops up, and that’s where the information comes from.
redirect
- (id)forwardingTargetForSelector:(SEL)aSelector
Copy the code
Message forwarding is triggered when dynamic method resolution returns NO without processing. The forwardInvocation: method is invoked at this point. Before the message forwarding mechanism implementation, the Runtime system will give us the opportunity to once removed, by overloading – (id) forwardingTargetForSelector: (SEL) aSelector method to replace the message receiver for other objects:
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if(aSelector == @selector(mysteriousMethod:)){
return alternateObject;
}
return [super forwardingTargetForSelector:aSelector];
}
Copy the code
forwarding
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
Copy the code
When forwardingTargetForSelector: methods haven’t made any response, will come forward process. Forward when the first call methodSignatureForSelector: method, generate NSMethodSignature type inside the method signature. When generating the signature object, you can specify target and SEL, which can be replaced with other parameters to forward the message to other objects.
[otherObject methodSignatureForSelector:otherSelector];
Copy the code
Once the NSMethodSignature signature object is generated, the forwardInvocation: method is called, which is the last step in message forwarding and will crash if the message is not processed at this point. The only argument to the message is an NSInvocation object, which encapsulates the original message and its parameters. We can implement the forwardInvocation: method to do some default processing for messages that cannot be processed, or to forward the message to another object for processing without throwing an error.
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([object respondsToSelector:[anInvocation selector]]) {
[anInvocation invokeWithTarget:object];
} else{ [super forwardInvocation:anInvocation]; }}Copy the code
The thing to note here is where the anInvocation comes from? Actually in forwardInvocation: before the message is sent, the Runtime system will send object methodSignatureForSelector: news, and to return to the method signature is used to generate NSInvocation object. So we rewrite forwardInvocation: at the same time also to rewrite methodSignatureForSelector: method, otherwise will be throw exceptions.
When an object cannot respond to a message because there is no method implementation, the runtime system notifies the object via forwardInvocation: message. Each object inherits the forwardInvocation: method from the NSObject class. Method of NSObject, however, simply call the doesNotRecognizeSelector:. By implementing our own forwardInvocation: method, we can forward messages to other objects in this method implementation.
ForwardInvocation: the method is like a distribution center for unrecognized messages that are forwarded to different receiving objects. Or it can act like a transport station and send all messages to the same receiving object. It can translate one message into another, or simply “eat” some messages, so there are no responses and no errors.
The forwardInvocation method can also provide the same response for different messages, depending on how the method is implemented. What this method provides is the ability to link different objects to a message chain.
Note: forwardInvocation: the method is only called if the message cannot be properly responded to in the message receiving object. So, if we want an object to forward the negotiate message to other objects, that object cannot have an negotiate method. Otherwise, forwardInvocation: will not be called.
In actual combat
We can override the resolveInstanceMethod: and resolveClassMethod: methods to implement instance methods and class methods, respectively. When the Runtime system cannot find a method to execute in the Cache or method table, it calls resolveInstanceMethod: or resolveClassMethod: to give the programmer a chance to dynamically add a method implementation.
2, redirect we create a Person class, in order to let the runtime system can run forwardingTargetForSelector: method, we first resolveInstanceMethod: return NO, code is as follows:
From the running results, we execute the [Person fly] method, hit the Car run method in the console, and finally realize the message forwarding.
Person *person = [[Person alloc] init];
[person fly];
Copy the code
3, forwarding, If we don’t realize forwardingTargetForSelector, the system will approach methodSignatureForSelector and forwardInvocation forwarding, the code is as follows:
From the running results, we execute the [Person fly] method, hit the Car run method in the console, and finally realize the message forwarding.
Note:
methodSignatureForSelector
Used to generate the method signature, which is given toforwardInvocation
The parameters in theNSInvocation
The call.unrecognized selector sent to instance
That’s whymethodSignatureForSelector
In this method, because I didn’t find itfly
An empty method signature is returned, causing the program to crash.
The above is the forwarding of the message, if you feel that I said the wrong place welcome to point out, we communicate more.
The resources
Principle of Objective-C message sending and forwarding mechanism