I. Message sending process

In the analysis of iOS objc_msgSend fast lookup process and iOS objc_msgSend slow lookup process, we analyzed that after the object receives the message, objc_msgSend will first be stored in the cache of the object class. Method list and superclass object cache, method list in turn to find select SEL corresponding function pointer IMP.

If not, because OC is a dynamic language, methods can continue to be added to the class at run time, so when an object receives an unreadable message, a message forwarding mechanism is activated, which tells the program what to do with the message.

Message forwarding can be divided into two stages. The first stage is called Dynamic method resolution: We ask the class that the current recipient belongs to, can dynamically add a method and handle the unknown selector, and if the recipient doesn’t have a dynamically added method or if the dynamically added method still can’t handle the unknown selector, There is no way for the current recipient to respond to the selector itself by dynamically adding methods, and then the second stage of message forwarding begins. The second stage can be divided into two steps. In the first step, the receiver checks whether there is any other object that can process the message. If there is, the object that processes the message is called backup receiver Replacement Receiver. Second, if there is no standby receiver, start the full message forwarding. The Runtime system puts all the message information into the NSInvocation object and gives the receiver another chance to handle the unknown selector. If this fails, It’s going to throw an exception called unrecognize Selector send to instance XXX.

But before throwing an unrecognized selector sent to… The Objective-C runtime gives you three chances to save the program before the exception:

  1. Dynamic method parsingMethod resolution
  2. Fast forwardFast forwarding
  3. Full message forwardingNormal forwarding

2. Method resolution

When an object receives an undecipherable message, it first calls a class method of its own class:

+ (BOOL)resolveInstanceMethod:(SEL)selector;
Copy the code

The argument to this method is the selector that objc_msgSend cannot handle, and the Boolean returned indicates whether the class can add an instance method to handle it. If the seletor is not an instance method but a class method, there would be a similar class method call:

+ (BOOL)resolveClassMethod:(SEL)selector;
Copy the code

The dynamic method resolution of an object method is described in the iOS objc_msgSend slow lookup process analysis. The dynamic method resolution of a class method is found in the metaclass of the class. It is a little different from the object method resolution.

+ (BOOL)resolveClassMethod:(SEL) SEL {NSLog(@"%@ ",NSStringFromSelector(SEL)); if (sel == @selector(sayNB)) { IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(lgClassMethod)); Method sayMMethod = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(lgClassMethod)); const char *type = method_getTypeEncoding(sayMMethod); return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type); } return [super resolveClassMethod:sel]; }Copy the code

Three, fast forwardingFast forwarding(Backup receiver)

Before looking for the backup receiver, you need to know its trigger condition:

  • Method 1:objcMsgLogEnabledControl switch
  • Mode 2: UseexternFunction declaration (externCan be placed in front of a variable or function to indicate that the variable or function definition is in another file, prompting the compiler to look for the definition in another module when it encounters the variable or function.
void instrumentObjcMessageSends(BOOL flag) { bool enable = flag; // Shortcut NOP if (objcMsgLogEnabled == enable) return; // If enabling, flush all method caches so we get some traces if (enable) _objc_flush_caches(Nil); // Sync our log file if (objcMsgLogFD ! = -1) fsync (objcMsgLogFD); objcMsgLogEnabled = enable; }Copy the code

In this step, the Runtime system provides a method for the current receiver to return a backup receiver to handle the unknown selector, which looks like this:

- (id)forwardingTargetForSelector:(SEL)selector;
Copy the code

If the current receiver can find or provide such an object, it is returned, if not nil. If LGTeacher does not have the sayHello method and LGStudent does, then LGStudent can be used as the backup receiver for quick forwarding of messages.

- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    return  [LGStudent alloc];
}
Copy the code

4. Complete message forwardingNormal forwarding

If we do not find a suitable backup receiver, then we come to the message slow forwarding process to start the full message forwarding mechanism:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
Copy the code

When the full message forwarding mechanism is turned on, the run-time system encapsulates all the details of the message into the NSInvocation object, giving the receiver one last chance to resolve the message that has not yet been processed. So, use it with the forwardInvocation:

- (void)forwardInvocation:(NSInvocation *)anInvocation
Copy the code

Implementing this method is as simple as changing the target and having the message invoked in the new target. You can also change the parameters, change the selector, etc., to make the application scenario more variable.

ForwardInvocation: the method is an unrecognized message distribution center that forwards the unrecognized message to a different message object, or to the same object, or translates the message to another message, or simply “eat” the message without responding and throwing an error. For example, in order to avoid the direct flash back, we can give users a hint when the message cannot be processed in this method, which is also a friendly user experience.

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector)); return [NSMethodSignature signatureWithObjCTypes:"v@:*"]; } - (void)forwardInvocation:(NSInvocation *)anInvocation{ NSLog(@"%s - %@",__func__,anInvocation); Target = [LGStudent alloc]; // Invocation invocation - Method [anInvocation invocation]; }Copy the code

V @:* : where the first character v represents the function return type void, the second character @ represents the type id of self, and the third character: represents the type SEL of _cmd. These symbols can be found by searching for Type Encodings in the developer documentation in Xcode. The more detailed official documentation for portals is here and not listed here.

V. Summary of message forwarding process

  1. If the object cannot respond to a selector, the message forwarding process is entered.
  2. The first phase asks the receiver, the class to which it belongs, to see if it can dynamically add methods to handle the current “unknown selector.” This is called dynamic method resolution.
  3. The second phase involves the “complete message forwarding mechanism”, and if the run-time system has completed the first phase, then the receiver itself cannot respond to messages containing the selector by dynamically adding methods. At this point, the run-time system requests the receiver to handle the call to the message-related method by other means. Is divided into two steps:
    1. : Asks the receiver to see if any other object can handle the message. If so, the run-time system forwards the message to that object, and the message forwarding process ends.
    2. : If there is no “backup receiver”, then the full message forwarding mechanism will be started, and the system will encapsulate all the details related to the message at runtimeNSInvocationObject to give the receiver one last chance to try to resolve the message that is not currently being processed.