The last article gave you a primer on objc_msgSend, and here’s a primer on portal objc_msgSend. So what happens when an object receives a message that cannot be sent via objc_msgSend?
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 is divided into two stages. The first stage is called Dynamic Method Resolution: The class that the current recipient belongs to is asked if it 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, then the current recipient has no way to respond to the unknown selector by dynamically adding a method. Then it enters the second stage of message forwarding. 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 the backup 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.
After reading the above paragraph, combined with the following message forwarding flow chart, you should be able to have a relatively vivid cognition of the whole message forwarding process:
Let’s look at each step in detail.
Dynamic method parsing
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 only way to handle an unknown selector at this stage is if the relevant code has already been written in advance, just waiting for the runtime to dynamically insert the selector into the class.
This scheme is often used to implement the @dynamic property, as is done with the property in NSManagerObject in coreData. Since CoreData properties need to be read from the database and then dynamically bound, instead of being implemented through automatically generated setters and getters. The getter and setter implementation methods for its properties are written at compile time, and the setter and getter methods are added at dynamic method resolution time.
Backup receiver
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.
We can use this to simulate some of the features of multiple inheritance. So for example, inside an object, there’s a bunch of other objects, and that object can, through this method, select an object that handles the selector and return it. From the outside, it looks as if the object is handling the object itself.
You can also use this process to create cool design patterns, such as decorator patterns. Specific examples will be filled up in the updates of subsequent articles.
Note here that messages forwarded through this step cannot be manipulated during this process.
Complete message forwarding
When both steps fail, the Runtime system creates an NSInvocation object that encapsulates the unprocessed message and its details. This object contains the Selector, target, and parameters. This step calls the following methods to forward the message:
- (void)forwardInvation:(NSInvocation*)invocation;
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.
When this method is implemented, if you find that the original forwardInvation method should not be called, you need to call the method of the same name of its parent class, so that all the classes in the inheritance system have a chance to handle the call request, all the way up to NSObject. If the method of class NSObject is finally called, an exception will be thrown in doesNotRocgnizeSelector style.
As mentioned above, messages cannot be processed in step 2 standby receivers. In the complete message forwarding, not only can you operate the message, but also can easily get all the information related to the message. As a result, some seemingly dark magic implementations are actually implemented in the process of complete message forwarding, such as JSPatch and Aspects, two key steps of the open source libraries.