Recently, I have studied the message forwarding mechanism of iOS. I hereby make a record and use a real example to make myself understand more deeply. This example will enumerate ways to avoid crashes when methods are not implemented. The forwarding of information is divided into three steps, through this example to see how to avoid the program crash caused by method not implemented in each step of forwarding.

Let’s start with the message forwarding process. We know that calling a method on an object means sending a message to that object to call its method. Suppose we have a Dog class and call its testFun method as follows:

Dog *testDog = [Dog new];
 [testDog performSelector:@selector(testFun)];
Copy the code

At this time, the message forwarding flow is as follows:

First retweet: If Dog attempts to resolve the method itself, the +(BOOL)resolveClassMethod (SEL) SEL (instance method) or +(BOOL)resolveInstanceMethod (SEL) SEL (class method) will execute, If the Dog class implements testFun, it is called and message forwarding ends.

Second retweet: Fast forward If the realization of the forwarding method has not been found for the first time, you will call the class – (id) forwardingTargetForSelector (SEL) begin message aSelector fast forward, if we can find an object to realize the call testFun method, We can return this object in this method, and it will execute the testFun method we called

Third retweet: Slowly forward If the second forward also can handle testFun method of the object is not found, then the Dog class – (NSMethodSignature *) methodSignatureForSelector (SEL) aSelector method is invoked, This method returns a testFun method signature. If the testFun method signature is returned, the Dog -(void)forwardInvocation (NSInvocation *)anInvocation method is called, DoesNotRecognizeSelector method is executed, causing an unrecognized selector sent to instance exception crash.

How can we take advantage of IOS’s message forwarding mechanism to avoid crashes when objects don’t implement a method? Let’s look at how to avoid at each step:

Interception crash on the first forward – message handling

If the Dog class does not implement testFun, Call +(BOOL)resolveClassMethod:(SEL) SEL (instance method) or +(BOOL)resolveInstanceMethod:(SEL) SEL (class method) +(BOOL)resolveClassMethod (SEL) SEL

NSLog(@"Dog testFun "); NSLog(@"Dog testFun "); } +(BOOL)resolveInstanceMethod:(SEL)sel{ if ([super resolveInstanceMethod:sel]) { return YES; }else{ class_addMethod(self, sel, (IMP)testFun, "v@:"); return YES; }}Copy the code

TestFun: resolveInstanceMethod () : resolveInstanceMethod () : resolveInstanceMethod () : resolveInstanceMethod () : resolveInstanceMethod (); If we have a testFun method implementation (c voice), the program runs normally output: Dog testFun

This is the first step in avoiding crashes through the message forwarding mechanism.

Intercept crash on second forward – fast forward

Fast forward stage call Dog class – (id) forwardingTargetForSelector (SEL) aSelector method, if we can’t achieve testFun approach, We need to create an object that implements this method and then throw it as the method’s handler. Suppose we have a SubDog class that implements testFun.

# pragma mark - the second step is fast forwarding - (id) forwardingTargetForSelector aSelector: (SEL) {if ([NSStringFromSelector (aSelector) isEqual:@"testFun"]) { return [SubDog new]; } return [super forwardingTargetForSelector:aSelector]; }Copy the code

It’s quite simple, just create an instance of SubDog and throw it, which handles testFun. After running, the methods in SubDog execute and the program runs normally. This is the operation to avoid crashes through the second step of the message forwarding mechanism.

Intercept crash on third forward – fast forward

Forward at this time to enter the third stage, will call here – (NSMethodSignature *) methodSignatureForSelector: (SEL) aSelector method signature return, If the invocation is returned, the -(void)forwardInvocation (NSInvocation *)anInvocation method is called. The general invocation is the same as in step 2. Return an object that can handle testFun methods:

# pragma mark - the third step slow forward - (NSMethodSignature *) methodSignatureForSelector aSelector: (SEL) {if ([super MethodSignatureForSelector: aSelector] = = nil) {/ / create a method signature then return NSMethodSignature * signature = [NSMethodSignature signatureWithObjCTypes:"v@:"]; return signature; } return [super methodSignatureForSelector:aSelector]; } -(void)forwardInvocation:(NSInvocation *)anInvocation{// create an object to handle testFun SubDog * SubDog = [SubDog new]; SEL sel = anInvocation.selector; if ([subDog respondsToSelector:sel]) { [anInvocation invokeWithTarget:subDog]; } else {/ /, if could not handle the call doesNotRecognizeSelector method returns collapse [self doesNotRecognizeSelector: sel]; }}Copy the code

After running, the methods in SubDog execute and the program runs normally. This is the operation to avoid crashes through step 3 of the message forwarding mechanism.

Much more can be done on the third retweet

On the third invocation we get the anInvocation parameter, which contains all the information for the method invocation. We can make some modifications to the anInvocation, increase or decrease the parameter, and then find the appropriate object to handle it. Or we can implement our own methods and do it ourselves. Examples are as follows:

#pragma mark -(void)showMessage:(NSString*)message{NSLog(@"message = %@",message); } -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ if ([super methodSignatureForSelector:aSelector] == nil) { NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:"]; return signature; } return [super methodSignatureForSelector:aSelector]; } -(void)forwardInvocation:(NSInvocation *)anInvocation{ SEL sel = @selector(showMessage:); NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:@"]; anInvocation = [NSInvocation invocationWithMethodSignature:signature]; [anInvocation setTarget:self]; [anInvocation setSelector:sel]; NSString *message = @" self-implemented method in step 3, changed argument "; [anInvocation setArgument:&message atIndex:2]; if ([self respondsToSelector:sel]) { [anInvocation invokeWithTarget:self]; }else{ [super forwardInvocation:anInvocation]; }}Copy the code

The method is just a testFun invocation without any arguments, but the anInvocation method was modified in -(void)for direction Invocation (NSInvocation *)anInvocation. Make it a call to an NSString argument, and then we implement the method and set ourselves as the implementation object of the method, and the program runs and says: message = the method we implemented in step 3, changed the argument

This is achieved in the third call in the operation, the code is very simple, but very interesting, try their own operation.

Other uses of message forwarding mechanism: 1.JSPatch –iOS dynamic update scheme utilizes the third forward of message forwarding mechanism 2. Realize multiple proxy 3. Realize multiple inheritance indirectly