I recently reviewed the message forwarding mechanism of iOS to make myself understand it better. Message forwarding is divided into three steps:

  1. Method resolution
  2. Fast forward
  3. Slowly forward

The whole process of message forwarding: we know that calling a method of an object is actually sending a message to the object to call its method Person. We call its sendMessage method as follows:

[[Person new] sendMessage:@"Hello"];
Copy the code

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

First retweet: Method parsing Person tries to parse the method himself, The Person class’s +(BOOL)resolveClassMethod (SEL) SEL (class method) or +(BOOL)resolveInstanceMethod (SEL) SEL (instance method) will execute, If the Person class implements the sendMessage: method, 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 forwarding, If we can find an object that implements the called sendMessage: method, we can return the object in this method and it will execute the sendMessage: method we called

Third forwarding: slow forwarding if the second forwarding is not found, sendMessage can be processed: Method of the object, then the Person class – (NSMethodSignature *) methodSignatureForSelector (SEL) aSelector method is invoked, this method returns a sendMessage: If sendMessage is returned: The Person class -(void)forwardInvocation (NSInvocation*)anInvocation method will be called, and if not, the method will be called from the Person class. DoesNotRecognizeSelector method is executed, causing an unrecognized selector to send to instance exception crash.

void sendMessage(id self, SEL _cmd, NSString * msg) { NSLog(@"----%@", msg); } @implementation Person // normal message forwarding + (BOOL)resolveInstanceMethod:(SEL) SEL {NSString * methodName = NSStringFromSelector(sel); if ([methodName isEqualToString:@"sendMessage:"]) { return class_addMethod(self, sel, (IMP)sendMessage, "v@:@"); } return NO; } / / fast forward - (id) forwardingTargetForSelector: (SEL) aSelector {nsstrings * methodName = NSStringFromSelector (aSelector); if ([methodName isEqualToString:@"sendMessage:"]) { return [Man new]; } return [super forwardingTargetForSelector:aSelector]; } / / 1. The method signature / / 2. Forward - (NSMethodSignature *) methodSignatureForSelector: (SEL) aSelector {nsstrings * methodName = NSStringFromSelector(aSelector); if ([methodName isEqualToString:@"sendMessage:"]) { return [NSMethodSignature signatureWithObjCTypes:"v@:@"]; } return [super methodSignatureForSelector:aSelector]; } - (void)forwardInvocation:(NSInvocation *)anInvocation { SEL sel = [anInvocation selector]; Man * tempObj = [Man new]; if ([tempObj respondsToSelector:sel]) { [anInvocation invokeWithTarget:tempObj]; }else { [super forwardInvocation:anInvocation]; }} - (void) doesNotRecognizeSelector: (SEL) aSelector {NSLog (@ "forward failure"); } @endCopy the code

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 Person class does not implement sendMessage: Method, Call +(BOOL)resolveClassMethod:(SEL) SEL (class method) or +(BOOL)resolveInstanceMethod:(SEL) SEL (instance method) +(BOOL)resolveInstanceMethod (SEL) SEL

Void sendMessage(id self, SEL _cmd, NSString * MSG) {NSLog(@"--%@", MSG); } +(BOOL)resolveInstanceMethod:(SEL)sel{ if ([super resolveInstanceMethod:sel]) { return YES; }else{ class_addMethod(self, sel, (IMP)sendMessage, "v@:@"); return YES; }}Copy the code

We need to implement a sendMessage ourselves: In resolveInstanceMethod, we will first determine whether our parent class can resolve the method. If not, we will use iOS Runtime to dynamically add sendMessage to our own class. The premise is that we have a sendMessage method implementation (C voice), the program runs normally output: –Hello

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 not achieve the sendMessage: Method, we need to create an object that implements the method and then throw it out as the method’s handler. Suppose we have a Man class that implements sendMessage,

As long as this method doesn’t return nil and self, the whole message sending process is restarted

In this case, the processing method is as follows:

/ / fast forward - (id) forwardingTargetForSelector: (SEL) aSelector {nsstrings * methodName = NSStringFromSelector (aSelector); if ([methodName isEqualToString:@"sendMessage:"]) { return [Man new]; } return [super forwardingTargetForSelector:aSelector]; }Copy the code

We just need to create an instance of Man and throw it, which will handle the sendMessage: method. After running, the method in Man will execute, and the program will run 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 aSelector: (SEL)

DoesNotRecognizeSelector message if return nil, then crash, if return a function signature, The NSInvocation object will be created and sent -(void)forwardInvocation:(NSInvocation *)anInvocation. The general method will be the same as in step 2. Also returns an object that can handle the sendMessage: method:

/ / 1. The method signature / / 2. Forward - (NSMethodSignature *) methodSignatureForSelector: (SEL) aSelector {nsstrings * methodName = NSStringFromSelector(aSelector); if ([methodName isEqualToString:@"sendMessage:"]) { return [NSMethodSignature signatureWithObjCTypes:"v@:@"]; } return [super methodSignatureForSelector:aSelector]; } - (void)forwardInvocation:(NSInvocation *)anInvocation { SEL sel = [anInvocation selector]; Man * tempObj = [Man new]; if ([tempObj respondsToSelector:sel]) { [anInvocation invokeWithTarget:tempObj]; }else { [super forwardInvocation:anInvocation]; }}Copy the code

After running, the method in Man will execute, and the program will run normally. This is the operation to avoid crashes through step 3 of the message forwarding mechanism.

If failed, will call the – (void) doesNotRecognizeSelector (SEL) aSelector method, to the system error, how to modify how to modify it

- (void)doesNotRecognizeSelector:(SEL)aSelector {
    NSLog(@"asdsa");
}
Copy the code

Extension: 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:

/ / 1. The method signature / / 2. Forward - (NSMethodSignature *) methodSignatureForSelector: (SEL) aSelector {nsstrings * methodName = NSStringFromSelector(aSelector); if ([methodName isEqualToString:@"sendMessage:"]) { return [NSMethodSignature signatureWithObjCTypes:"v@:@"]; } return [super methodSignatureForSelector:aSelector]; } - (void)forwardInvocation:(NSInvocation *)anInvocation SEL SEL = @selector(sendMessage2:); NSMethodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:@"]; anInvocation = [NSInvocation invocationWithMethodSignature:signature]; // Add target [anInvocation setTarget:self]; [anInvocation setSelector:sel]; // add argument NSString *message = @" modified argument "; [anInvocation setArgument:&message atIndex:2]; if ([self respondsToSelector:sel]) { [anInvocation invokeWithTarget:self]; }else{ [super forwardInvocation:anInvocation]; } } - (void)sendMessage2:(NSString *)msg { NSLog(@"--%@", msg); // -- modified parameters}Copy the code

Originally the method we called was just sendMessage: with no arguments, But in the -(void)forwardInvocation:(NSInvocation *)anInvocation method we changed the anInvocation to a call with an NSString argument, Then we implement the method and set ourselves as the implementation object of the method. After the program runs, we print: — modified parameters

In this way, a simple method and parameter replacement operation, the code is not complex, you can try

PS: Other uses of message forwarding mechanism: 1. Implementation of multiple proxies 2. Indirect implementation of multiple inheritance


End