We know in Objective-C that sending a Message to an object that it can’t handle leads to the Message Forwarding process described below, but why design such a complex process?

Message forwarding can be divided into three phases. The names of each phase vary from source to source, and apple’s official documentation does not specify these three phases, so the phase names here are for reference only.

Let’s answer these questions by looking at each stage in detail.

Phase 1: Dynamic Method Resolution

In some cases, you want to be able to dynamically provide implementations for a method. For example, in Objective-C you can declare a property as @dynamic

@dynamic propertyName;
Copy the code

This tells the compiler that the setter and getter methods associated with the property will be added dynamically. The compiler does not automatically create setters and getters and corresponding member variables (instance variables, or Ivar) for you.

You can dynamically add an implementation for a given selector by implementing the methods resolveInstanceMethod: or resolveClassMethod:.

An Objective-C method is nothing more than a C function that takes at least two arguments — self and _cmd. You can add a function to a class using the class_addMethod function. You need to provide functions like the following:

void dynamicMethodIMP(id self, SEL _cmd) {
    // implementation ....
}
Copy the code

In the process of message forwarding, use resolveInstanceMethod: to dynamically add a function to a method of a class:

@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
    if (aSEL == @selector(resolveThisMethodDynamically)) {
          class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
          return YES;
    }
    return [super resolveInstanceMethod:aSEL];
}
@end
Copy the code

The last parameter to class_addMethod is called types, which is a string describing the type of the method’s arguments.

V for void, @ for object or ID type, and: for method selector SEL. For details, see objective-C Runtime Programming Guide->Type Encodings

DynamicMethodIMP (void, id, SEL); dynamicMethodIMP (void, id, SEL);

The point of this phase is to dynamically provide a method implementation for a class. Strictly speaking, the message forwarding process has not entered. RespondsToSelector: and instancesRespondToSelector: also called resolveInstanceMethod:. That is to say, if resolveInstanceMethod: returned to YES, then respondsToSelector: and instancesRespondToSelector: will return YES.

In CoreData, some attributes are labeled @dynamic, and the values behind these attributes are updated and retrieved from the database without requiring a member variable. So resolveInstanceMethod: is implemented for setter and getter methods for these properties, returns YES, and sets or retrieves the value of the property from the database.

Phase 2: Replace the message receiver (fast forwarding)

If the first stage resolveInstanceMethod: returned to the NO, is called forwardingTargetForSelector: ask if the message is forwarded to another object. This is equivalent to changing the first parameter of objc_msgSend to another object, and the receiver of the message has changed.

- (id)forwardingTargetForSelector:(SEL)aSelector {
    return someOtherObject;
}
Copy the code

Stage 3: Full message forwarding mechanism

If the second stage forwardingTargetForSelector: returns nil, it entered the so-called completely message forwarding mechanism.

First call methodSignatureForSelector: return to forward the message correct signature:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if ([NSStringFromSelector(aSelector) isEqualToString:@"foo"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}
Copy the code

When the correct signature is returned, forwardInvocation: the message is forwarded

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"forwardInvocation");
    SomeOtherObject *someOtherObject = [SomeOtherObject new];
    if ([someOtherObject respondsToSelector:[anInvocation selector]]) {
        [anInvocation invokeWithTarget:someOtherObject];
    } else{ [super forwardInvocation:anInvocation]; }}Copy the code

The above code forwards the message to other objects, which is exactly what the sample code in the second phase does. The difference is that there is an NSInvocation object at this stage.

NSInvocation is an object used to store and forward messages. It contains all the elements of an Objective-C message: a target, a selector, arguments, and return values. Each element can be set directly.

So unlike stage 2, in this stage you can:

  • Store the message and forward it when you see fit, or leave the message alone.
  • Modify message target, selector, parameters, etc
  • Forward the message multiple times to multiple objects

Obviously at this stage, you can do more with an OC message.

conclusion

The significance of the first stage is to dynamically add method implementation, the second stage directly forward the message to other objects, the third stage is the expansion of the second stage, can realize multiple forwarding, forwarding to multiple objects, etc. This is perhaps the significance of the three stages of design.

In addition, an object that responds to a message through message forwarding “looks like” inheriting a method implementation defined in another class, which translates into multiple inheritance.

Of course, there are many inheritances that shouldn’t exist at all. You should follow object-oriented design principles such as “single responsibility,” “high cohesion, low coupling,” and design the functionality of your classes properly.

References:

  1. Objective-C Runtime Programming Guide

  2. The Effective Objective – 2.0 C

  3. Principle of Objective-C message sending and forwarding mechanism

  4. NSInvocation