1, the preface
In iOS projects, we often encounter x[xx xx]: Unrecognized selector sent to instance XXX crash, the classic crash will appear when calling a method that is not implemented by the class. Next, I will analyze the reasons and how to avoid the crash step by step.
2. Dynamic method resolution
1. _class_resolveMethod analysis
When calling a method that is not implemented by the class, we will first look for the method in the method list of this class and its parent class. If not, we will enter the dynamic method resolution _class_resolveMethod, which is also an opportunity given by Apple dad to prevent crash, so that we can have more dynamic. How to prevent crash? And then we look down.
_class_resolveMethod(Class CLS, SEL SEL, id INst). CLS is a Class and INST is an instance object when the instance method is dynamically resolved. CLS is a metaclass and INst is a Class when the instance method is dynamically resolved.
if(resolver && ! triedResolver) { ... _class_resolveMethod(cls, sel, inst); . goto retry; } void _class_resolveMethod(Class CLS, SEL SEL, id inst) {// Check whether the current is a metaclassif(! CLS ->isMetaClass()) {class_resolveInstancemethod (CLS, sel, inst); }else_class_resolveClassMethod(CLS, sel, inst);if(! LookUpImpOrNil (CLS, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) { _class_resolveInstanceMethod(CLS, SEL, INST); }}}Copy the code
There are two cases in this method, one is object method resolution and the other is class method resolution.
2. Object method resolution
/***********************************************************************
* _class_resolveInstanceMethod
* Call +resolveInstanceMethod, looking fora method to be added to class cls. **********************************************************************/ static void _class_resolveInstanceMethod(Class cls, SEL sel, {SEL_resolveInstanceMethod: SEL_resolveInstanceMethod: SEL_resolveInstanceMethod: SEL_resolveInstanceMethod: SEL_resolveInstanceMethod: SEL_resolveInstanceMethod: SEL_resolveInstanceMethod: SEL_resolveInstanceMethod: SEL_resolveInstanceMethod If not, the CLS will not send a resolveInstanceMethod message and will not report that the resolveInstanceMethod cannot be foundif (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return; } // This class implements the class method resolveInstanceMethod // when the object cannot find a method to call, the system will respond to the method. BOOL (* MSG)(Class, SEL, SEL) = (typeof(MSG))objc_msgSend; bool resolved = msg(cls, SEL_resolveInstanceMethod, sel); IMP = lookUpImpOrNil(CLS, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/); // Omit some unimportant error message code... }Copy the code
3. _class_resolveInstanceMethod summary
- 1. In
_class_resolveInstanceMethod
The first thing to look for is the class methodresolveInstanceMethod
Whether to implement, if this class does not implement directly return null, if their implementation will go to the next step. - 2. The next step will be sent to this class
msg(cls, SEL_resolveInstanceMethod, sel)
Message, and this class is not implemented, but the final error is not foundresolveInstanceMethod
Method, so it’s a little weird, is that a superclass implementation? By global searchresolveInstanceMethod
And, ultimately, inNSObject
It finds an implementation of this method, so it goes toNSObject
The implementation ofNO
. - 3) It will pass
lookUpImpOrNil
Try again to find the implementation of the method and crash if you don’t find it. - 4. Because the whole crash is because we can’t find a way to implement, so if we implement ourselves in this class
resolveInstanceMethod
When there is no way to find the implementation will eventually go toresolveInstanceMethod
Inside, add dynamically to the method that this class does not implementimp
The last timelookUpImpOrNil
I’ll find the correspondingimp
Return, so that the project does not resultcrash
. - 5.
resolveInstanceMethod
It’s the system that gives us the opportunity to target what hasn’t been implementedsel
Perform custom operations. - The solution is as follows
// resolveInstanceMethod:(SEL) SEL {NSLog(@"Here we go, buddy. - % P.",sel);
if (sel == @selector(saySomething)) {
NSLog(@"Speak.");
IMP sayHIMP = class_getMethodImplementation(self, @selector(studentSayHello));
Method sayHMethod = class_getInstanceMethod(self, @selector(studentSayHello));
const char *sayHType = method_getTypeEncoding(sayHMethod);
return class_addMethod(self, sel, sayHIMP, sayHType);
}
return[super resolveInstanceMethod:sel]; + (BOOL)resolveClassMethod:(SEL) SEL {NSLog(@)"Class method here, brother - %p",sel);
if (sel == @selector(studentSayLove)) {
NSLog(@"Say you love me.");
IMP sayHIMP = class_getMethodImplementation(objc_getMetaClass("Student"), @selector(studentSayObjc));
Method sayHMethod = class_getInstanceMethod(objc_getMetaClass("Student"), @selector(studentSayObjc));
const char *sayHType = method_getTypeEncoding(sayHMethod);
return class_addMethod(objc_getMetaClass("Student"), sel, sayHIMP, sayHType);
}
return [super resolveInstanceMethod:sel];
}
Copy the code
3. Class method resolution
_class_resolveClassMethod has the same logic as _class_resolveInstanceMethod, except that class methods are handled in metaclasses.
/***********************************************************************
* _class_resolveClassMethod
* Call +resolveClassMethod, looking fora method to be added to class cls. **********************************************************************/ static void _class_resolveClassMethod(Class cls, SEL sel, id inst) { assert(cls->isMetaClass()); ResolveClassMethod (resolveClassMethod)if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return; } // Send resolveClassMethod BOOL (* MSG)(Class, SEL, SEL) = (typeof(MSG))objc_msgSend; // _class_getNonMetaClass initializes the metaclass and determines whether it is the root metaclass. Bool resolved = MSG (_class_getNonMetaClass(CLS, INST), SEL_resolveClassMethod, sel); IMP = lookUpImpOrNil(CLS, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/); // Omit some unimportant error message code... }Copy the code
4. Class methods need to be parsed twice
_class_resolveClassMethod(cls, sel, inst);
if(! LookUpImpOrNil (CLS, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) { _class_resolveInstanceMethod(cls, sel, inst); }Copy the code
Since both object method resolutions and class method resolutions go through _class_resolveInstanceMethod and end up in NSObject, let’s override the resolveInstanceMethod method in the NSObject class. In this method in the face of no implementation of the method (whether class method or object method) dynamic add IMP, and then custom processing (such as playing a box said the network is not good, in the background bug collection), is not flattered.
NSObject+crash.m
+ (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(sayMaster));
Method sayHMethod = class_getInstanceMethod(self, @selector(sayMaster));
const char *sayHType = method_getTypeEncoding(sayHMethod);
return class_addMethod(self, sel, sayHIMP, sayHType);
}
if(xx) {// Background bug collection or some other custom processing}}Copy the code
3. Message forwarding
1. The fast forward forwardingTargetForSelector
When they do not have a dynamic method resolution, they will come to our message forwarding, and what about message forwarding? Through instrumentObjcMessageSends (true); Function to set whether to send a log and store it in/TMP /msgSends-“xx”;
Student *student = [[Student alloc] init];
instrumentObjcMessageSends(true);
[student saySomething];
instrumentObjcMessageSends(false);
Copy the code
The log output is as follows:
forwardingTargetForSelector
command + shift + 0
forwardingTargetForSelector
If an object implements (or inherits) this method, and returns a non-nil (and non-self) result, that returned object is used as the new receiver object and the message dispatch resumes to that new object. (Obviously if you return self from this method, the code would just fall into an infinite loop.)
If an object implements (or inherits) this method and returns a non-nil (and non-self) result, the returned object is used as the new receiver object, and message dispatch continues to the new object.
Person.m
- (void)studentSaySomething {
NSLog(@"Person-%s",__func__);
}
Student.m
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(studentSaySomething)) {
return [Person new];
}
return [super forwardingTargetForSelector:aSelector];
}
Copy the code
The method to realize the Student not realized in Person, then forwardingTargetForSelector redirect to the Person, that would not cause crashes.
2. Slow forward methodSignatureForSelector
When we don’t have handled in the fast forwarding forwardingTargetForSelector or redirect object without processing, will come slowly forward methodSignatureForSelector. By looking at the official documentation, methodSignatureForSelector forwardInvocation collocation method are used together, specific can go to see the official document.
methodSignatureForSelector
Returns thesel
The returned signature is wrapped according to the method’s parameters. This function gives the overloading party the opportunity to throw a function signature, followed by the followingforwardInvocation
To carry out.forwardInvocation
NSInvocation can be forwarded to multiple objects multiple times.
Person.m
- (void)studentSaySomething {
NSLog(@"Person-%s",__func__);
}
Teacher.m
- (void)studentSaySomething {
NSLog(@"Person-%s",__func__);
}
Student.m
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"Student-%s",__func__); // Determine if the selector is to be forwarded, if so, manually generate the method signature and return it.if (aSelector == @selector(studentSaySomething)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"Student-%s",__func__);
// SEL aSelector = [anInvocation selector];
// if ([[Person new] respondsToSelector:aSelector])
// [anInvocation invokeWithTarget:[Person new]];
// else
// [super forwardInvocation:anInvocation];
// if ([[Teacher new] respondsToSelector:aSelector])
// [anInvocation invokeWithTarget:[Teacher new]];
// else
// [super forwardInvocation:anInvocation];
}
Copy the code
If forwardInvocation didn’t do anything, just methodSignatureForSelector returned to the signature, then nothing happens, also won’t break down.
Slow forwarding is similar to fast forwarding in that A method of class A is forwarded to the implementation of class B. , forwardInvocation forward relatively more flexible and forward forwardingTargetForSelector can only be fixed to an object, forwardInvocation allows us to forward to multiple objects.
3. Message cannot be processed doesNotRecognizeSelector
/ / quote exception error - (void) doesNotRecognizeSelector (SEL) SEL {_objc_fatal ("-[%s %s]: unrecognized selector sent to instance %p",
object_getClassName(self), sel_getName(sel), self);
}
Copy the code
4, summarize
- 1. When dynamic method resolution
resolveInstanceMethod
returnNO
, will comeforwardingTargetForSelector:
, get the newtarget
As areceiver
To performselector
, if returnnil
Or the returned object is not processed, go to step 2. - 2.
methodSignatureForSelector
After obtaining the method signature, determine whether the returned type information is correct, and then callforwardInvocation
performNSInvocation
Object and returns the result. If the object does not implement methodSignatureForSelector, step into the third. - 3.
doesNotRecognizeSelector:
An exception is thrownunrecognized selector sent to instance %p
. - Attached below is my summary of the diagram