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_resolveInstanceMethodThe first thing to look for is the class methodresolveInstanceMethodWhether 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 classmsg(cls, SEL_resolveInstanceMethod, sel)Message, and this class is not implemented, but the final error is not foundresolveInstanceMethodMethod, so it’s a little weird, is that a superclass implementation? By global searchresolveInstanceMethodAnd, ultimately, inNSObjectIt finds an implementation of this method, so it goes toNSObjectThe implementation ofNO.
  • 3) It will passlookUpImpOrNilTry 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 classresolveInstanceMethodWhen there is no way to find the implementation will eventually go toresolveInstanceMethodInside, add dynamically to the method that this class does not implementimpThe last timelookUpImpOrNilI’ll find the correspondingimpReturn, so that the project does not resultcrash.
  • 5.resolveInstanceMethodIt’s the system that gives us the opportunity to target what hasn’t been implementedselPerform 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.

  • methodSignatureForSelectorReturns theselThe 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 followingforwardInvocationTo carry out.
  • forwardInvocationNSInvocation 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 resolutionresolveInstanceMethodreturnNO, will comeforwardingTargetForSelector:, get the newtargetAs areceiverTo performselector, if returnnilOr the returned object is not processed, go to step 2.
  • 2.methodSignatureForSelectorAfter obtaining the method signature, determine whether the returned type information is correct, and then callforwardInvocationperformNSInvocationObject 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