OC method calls send messages, and if a method with the corresponding name cannot be found, there are three opportunities to remedy it before Crash occurs.
- Dynamic Method Analysis
- Standby Receiver
- Message forwarding
Dynamic Method Analysis
When an object receives an unknown message, it first calls the class method of its class + resolveInstanceMethod: (instance method) or + resolveClassMethod: (class method).
This method returns the Bool type, indicating whether the class can add a new method to handle this situation. Using this method, we need to write the corresponding method to implement the code, and add insertion at run time.
This method is often used to implement the setter getter method for the @dynamic attribute
e.g.
// // TestObjA.h // test // @interface TestObjA : NSObject @property(nonatomic , strong) NSString *testStr; @end // // TestObjA.m // test // @implementation TestObjA @dynamic testStr; @end
In this case, crash occurs by calling setter and getter methods
TestObjA *obj = [[TestObjA alloc] init]; [obj setTestStr:@"haha"]; NSString *result = [obj testStr]; NSLog(result);
The following demonstrates how to implement the @dynamic attribute with this method
// // TestObjA.h // test // @interface TestObjA : NSObject @property(nonatomic , strong) NSString *testStr; @property (nonatomic, strong) NSString *storeStr; @end // // TestObjA.m // test // void dynamicStrSetter (id self,SEL _cmd, id value) { TestObjA *obj = (TestObjA *)self; obj.storeStr = (NSString *)value; } id dynamicStrGetter (id self,SEL _cmd) { TestObjA *obj = (TestObjA *)self; return obj.storeStr; } @implementation TestObjA @dynamic testStr; + (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(setTestStr:)) { class_addMethod(self, sel, (IMP)dynamicStrSetter, "v@:@"); return true; }else if(sel == @selector(testStr)) { class_addMethod(self, sel, (IMP)dynamicStrGetter, "@@:"); return true; } return [super resolveClassMethod:sel]; }; @end
In this way, when calling setter and getter methods, the C language method implemented before will be invoked to ensure the normal operation of the program.
Standby Receiver
If the previous step is not implemented, the following method will continue to be called
- (id)forwardingTargetForSelector:(SEL)aSelector
This method returns a non-empty object that acts as a new recipient of the method and sends the message again.
e.g.
Create a new TestObjB object
// // TestObjB.h // test // @interface TestObjB : NSObject - (void)testFunc; @end // // TestObjB.m // test // @implementation TestObjB - (void)testFunc { NSLog(@"test"); } @end
Then implement the following method in TestObjA
- (id)forwardingTargetForSelector:(SEL)aSelector { return [[TestObjB alloc] init]; }
Now let's do another test:
TestObjA *obj = [[TestObjA alloc] init]; [obj performSelector:@selector(testFunc) withObject:nil];
Print output will be found: test
Message forwarding
Delete the code from Step 2 TestObjA above and add the code:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSMethodSignature *signature = [super methodSignatureForSelector:aSelector]; if (!signature) { if ([TestObjB instancesRespondToSelector:aSelector]) { signature = [TestObjB instanceMethodSignatureForSelector:aSelector]; } } return signature; } - (void)forwardInvocation:(NSInvocation *)anInvocation { if ([TestObjB instancesRespondToSelector:anInvocation.selector]) { [anInvocation invokeWithTarget:[[TestObjB alloc] init]]; } }
Then run the last test code, and the results can still be printed out:test
At this stage, a method signature is generated by calling - (NSMethodSignature*) method Signature ForSelector:(SEL)aSelector
Then call the -(void) forward Invocation:(NSInvocation *) anInvocation method for message forwarding.
Note: Invocation initialization requires method signature: NSMethod Signature
So far, three steps have been completed to intercept an unknown message crash. If it is not processed, the forward Invocation: method of NSObject is called at runtime. The implementation of this method simply calls the doesNotRecognizeSelector: method, which does not forward any messages and throws exceptions directly.