As you learned in the previous chapter, OC calls the methods, and the underlying call is objc_msgSend to send a message. When sending a message will go through a series of fast search, slow search, if the search to the corresponding IMP, directly return; If it does not, it enters the dynamic parsing and message forwarding process of the method.
1. Dynamic method resolution
By exploring objc_msgSend source code, when the slow search still does not find IMP, will enter the method dynamic parsing stage, source code is as follows:
_class_resolveMethod
retry
_class_resolveMethod
_class_resolveMethod
Void _class_resolveMethod(Class CLS, SEL SEL, id inst) {// Whether it is a metaclassif (! 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); // It has been processedif(! lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/) {// object method resolver _class_resolveInstanceMethod(CLS, sel, inst); }}}Copy the code
Because class methods are stored in metaclasses, they are handled differently in _class_resolveMethod
Metaclass: indicates that the class methods in the metaclass are processed, but the methods in the metaclass are stored in the form of instance methods in the root metaclass. Therefore, the instance method of the root metaclass is eventually searched, and the instance method is called to parse the search. Non-metaclasses: Handle instance methods stored in a class.Copy the code
1.1 Instance method dynamically resolves _class_resolveInstanceMethod
Dynamic parsing of instance methods in _class_resolveInstanceMethod method, source code is as follows:
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst) { // 1. Check whether the system implements SEL_resolveInstanceMethod // that is +(BOOL)resolveInstanceMethod:(SEL) SEL, // inherited from NSObject class, default implementation, return NOif(! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) {// Resolver not implemented. There is no need for dynamic parsingreturn; BOOL (* MSG)(Class, sel, sel) = (typeof(MSG))objc_msgSend; bool resolved = msg(cls, SEL_resolveInstanceMethod, sel); 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() ? '+' : The '-',
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
+(BOOL)resolveInstanceMethod (SEL) SEL method in the unimplemented method specified IMP, and add to the class, implement method dynamic resolution, prevent system crash.
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"Here we go, buddy :%s - %@",__func__,NSStringFromSelector(sel));
if (sel == @selector(saySomething)) {
NSLog(@"Speak.");
IMP sayHIMP = class_getMethodImplementation(self, @selector(sayHello));
Method sayHMethod = class_getInstanceMethod(self, @selector(sayHello));
const char *sayHType = method_getTypeEncoding(sayHMethod);
return class_addMethod(self, sel, sayHIMP, sayHType);
}
return [super resolveInstanceMethod:sel];
}
Copy the code
We can also collect bugs in this method based on the prefix, route, transaction, jump to different pages.
1.2 _class_resolveClassMethod
If it is a metaclass, the related class method is dynamically resolved in the _class_resolveClassMethod method. The implementation steps of this method are similar to the implementation steps of the instance method, except that the metaclass is obtained when the message is sent, that is:
_class_resolveClassMethod
static void _class_resolveClassMethod(Class cls, SEL sel, id inst) { assert(cls->isMetaClass()); SEL_resolveClassMethod :(SEL) SEL, resolveClassMethod:(SEL) SEL, resolveClassMethod:(SEL) SEL, resolveClassMethod:(SEL) SEL, resolveClassMethod:(SEL) SEL, resolveClassMethod:(SEL) SEL, resolveClassMethod:(SEL) SEL, resolveClassMethodif (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return; +(BOOL)resolveClassMethod:(SEL) SEL method 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 //3. 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
+ (BOOL)resolveClassMethod (SEL) SEL
+ (BOOL)resolveClassMethod:(SEL)sel{
NSLog(@"Here comes class method :%s - %@",__func__,NSStringFromSelector(sel));
if (sel == @selector(sayLove)) {
NSLog(@"Say it. - Say you you love me."); IMP sayHIMP = class_getMethodImplementation(self, @selector(sayObjc)); Method sayHMethod = class_getClassMethod(self, @selector(sayObjc)); const char *sayHType = method_getTypeEncoding(sayHMethod); // Class methods in the metaclass objc_getMetaClass("LGStudent")
return class_addMethod(self, sel, sayHIMP, sayHType);
}
return [super resolveClassMethod:sel];
}
Copy the code
summary
- Methods first go through the cache search, method list search, and then enter the dynamic method parsing stage
- Instance method resolution needs to be implemented
resolveInstanceMethod
methods - Class method resolution needs to be implemented
resolveClassMethod
methods - Class methods are stored in metaclasses because
The metaclass
Inherited fromA metaclass
.A metaclass
Ultimately inherited fromNSObject
So when you parse a class method, you’ll eventually find itNSObject
. Because the metaclass and root class are created by the system and cannot be modified, you can recreate the parent class of the root metaclassNSObject
In, add the corresponding instance methodresolveInstanceMethod
Dynamic parsing.
2. Forwarding messages
In the process of method search, after cache search, method list search and dynamic method analysis, if the above steps are not found to IMP, there is no method dynamic analysis, then it will enter the last step, crash.
_objc_msgForward_impcache
STATIC_ENTRY __objc_msgForward_impcache // Method cache version // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band Z is 0 (EQ)for normal, 1 (NE) for stret
beq __objc_msgForward
b __objc_msgForward_stret
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
// Non-stret version
MI_GET_EXTERN(r12, __objc_forward_handler)
ldr r12, [r12]
bx r12
END_ENTRY __objc_msgForward
ENTRY __objc_msgForward_stret
Copy the code
In _objc_msgForward_impcache, call __objc_msgForward, then call __objc_forward_handler, override the _objc_forward_handler OC method as follows, and then a classic crash.
So why is a stack like the one above printed during a crash?
lookUpImpOrForward
IMP
log_and_fill_cache
log_and_fill_cache
objcMsgLogEnabled
/tmp/msgSends
objcMsgLogEnabled
instrumentObjcMessageSends
Check the file in the/TMP/msgtimeline directory, as shown below:
resolveInstanceMethod:
forwardingTargetForSelector
methodSignatureForSelector
doesNotRecognizeSelector
2.1 Fast forwarding process
By looking at the forwardingTargetForSelector official documentation,
- (id)forwardingTargetForSelector:(SEL)aSelector
1. Return an object. If the object is non-empty and non-nil, the system forwards the message to the object for execution, otherwise, it continues to look for other processes. The system gives the SEL the opportunity to be passed to other objects. 2. If nil is returned or message forwarding is not processed, the forwardInvocation: method is used to process the message forwarding process.Copy the code
You can use the following code to forward the message of the saySomething method to the LGTeacher class without causing a system crash, so that the message is quickly forwarded.
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(saySomething)) {
return [LGTeacher alloc];
}
return [super forwardingTargetForSelector:aSelector];
}
Copy the code
2.2 Slow Forwarding Process
Into slow search process, you must first achieve methodSignatureForSelector method, return to a signature, this method signature encapsulates the return value type, the parameter type and other information.
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(saySomething)) { // v @ :
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
Copy the code
Then you must also implement – (void)forwardInvocation:(NSInvocation *)anInvocation;
- (void)forwardInvocation:(NSInvocation *)invocation
{
SEL aSelector = [invocation selector];
if ([friend respondsToSelector:aSelector])
[invocation invokeWithTarget:friend];
else
[super forwardInvocation:invocation];
}
Copy the code
Note:
1. ForwardInvocation method and methodSignatureForSelector method must be implemented at the same time 2. MethodSignatureForSelector generates a signature, NSInvocation object, 3. Pass the NSInvocation object as a parameter to the forwardInvocation method. The forwardInvocation method gives the message to the object that can handle the message to avoid a crash when the object calls the didNotRecognizeSelector method. 4. The forwardInvocation method is similar to the message being stored as a transaction. Here who can operate in this inside the operation, even if not the operation will not crash, here is the last processing opportunity to prevent crash.Copy the code
Let’s look at the forwardInvocation implementation in NSObject:
+ (void)forwardInvocation:(NSInvocation *)invocation {
[self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}
// Replaced by CF (throws an NSException)
+ (void)doesNotRecognizeSelector:(SEL)sel {
_objc_fatal("+[%s %s]: unrecognized selector sent to instance %p",
class_getName(self), sel_getName(sel), self);
}
// Replaced by CF (throws an NSException)
- (void)doesNotRecognizeSelector:(SEL)sel {
_objc_fatal("-[%s %s]: unrecognized selector sent to instance %p",
object_getClassName(self), sel_getName(sel), self);
}
Copy the code
This shows that the system does not throw an exception in the doesnot need cognizeselector method, so if the forwardInvocation method is overwritten, it will not crash regardless of whether there is an implementation in it, or if the parent method is invoked.
Message forwarding flowchart:
conclusion
When an unimplemented method is called, there are three solutions:
1, resolveInstanceMethod: object to send the message to add an IMP, 2, and then let the object handle forwardingTargetForSelector: Will forward the message to can handle the message object 3, methodSignatureForSelector and forwardInvocation: The first method generates the method signature, then creates the NSInvocation object as a parameter to the second method, then does the message processing in the forwardInvocation method, so long as the parent method is not executed in the second method, it will not crashCopy the code
What about the resolveInstanceMethod method being called twice?
- When looking for
IMP
Is called for the first time when the method dynamic parsing is entered if it is not foundresolveInstanceMethod
- Then enter the message forwarding process, call
forwardingTargetForSelector
, forwards the message to an object that can process the message - call
methodSignatureForSelector
andforwardInvocation
, return signature - call
forwardInvocation
resolveInstanceMethod
class_getInstanceMethod
resolveInstanceMethod