This article belongs to “Jane Book — Liu Xiaozhuang” original, please note:

< Jane books – Liu Xiaozhuang > https://www.jianshu.com/p/f313e8e32946



When an object’s methods are called, the list of methods is first looked up in the object’s class, and if there are none in the current class, the parent class, all the way to the root class, NSObject. If no method is found, the message forwarding step is entered.

Dynamic message parsing

When a method is not implemented, no corresponding method is found in the method list of the cache LSIT and its inheritance relationship. The message forwarding phase is then entered, but before the message forwarding phase is entered, the Runtime gives an opportunity to dynamically add method implementations.

You can dynamically add unimplemented methods by overriding the resolveInstanceMethod: and resolveClassMethod: methods. The first is to add instance methods and the second is to add class methods. Both methods return a BOOL, and return NO to enter the message forwarding mechanism.

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

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

When dynamically adding the implementation through class_addMethod function, there is a “v@:” at the end to describe SEL corresponding function implementation, specific description can refer to the official document.

Message Forwarding

Before for message forwarding, also can be in forwardingTargetForSelector: method unrealized, forwarded to other objects. You can return other objects that respond to unimplemented methods in the following methods.

- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSString *selectorName = NSStringFromSelector(aSelector);
    if ([selectorName isEqualToString:@"selector"]) {
        return object;
    }
    return [super forwardingTargetForSelector:aSelector];
}
Copy the code

When forwardingTargetForSelector: methods haven’t made any response, will come forward process. Forward when the first call methodSignatureForSelector: method, generate NSMethodSignature type inside the method signature. When generating the signature object, you can specify target and SEL, which can be replaced with other parameters to forward the message to other objects.

[otherObject methodSignatureForSelector:otherSelector];
Copy the code

Once the NSMethodSignature signature object is generated, the forwardInvocation: method is called, which is the last step in message forwarding and will crash if the message is not processed at this point.

The method passes in an NSInvocation object that is created from the generated signature object and invokeWithTarget: from the invocation of another object’s method.

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    if ([object respondsToSelector:[anInvocation selector]]) {
        [anInvocation invokeWithTarget:object];
    } else{[superforwardInvocation:anInvocation]; }}Copy the code

forward

Sending a message to an unprocessable object causes a crash, but the system gives the responding object a chance to handle the exception before crashing.

When an object cannot handle a message, the system invoishes the responder’s forwardInvocation: method and passes in a NSInvocation object, which contains the original message and parameters, before the Crash occurs. This method is called only if the method is not implemented.

You can implement the forwardInvocation: method to forward the message to another object. The forwardInvocation: method is a dynamic method that is called when the responder cannot respond to the method and can be overridden for message forwarding.

ForwardInvocation in message forwarding: what you need to do is confirm where the message will be sent and send the message with the original parameters. The forwarded message can be sent using the invokeWithTarget: method. Upon invoking the Invoke method, the return value of the original method is returned to the caller.

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    if ([someOtherObject respondsToSelector:[anInvocation selector]]) {
        [anInvocation invokeWithTarget:someOtherObject];
    } else{[superforwardInvocation:anInvocation]; }}Copy the code

The forwardInvocation method can handle more than one method, using Selector to handle multiple methods that need to be forwarded.

Dynamic method parsing

In OC, methods can sometimes be provided dynamically. For example, properties can be provided via @dynamic propertyName; Represents a property method that will be supplied dynamically at run time.

If you want to implement dynamic method resolution, you need to implement resolveInstanceMethod: or resolveClassMethod: methods to dynamically add method implementation. The class_addMethod method is used to dynamically add a method by associating it with a function pointer. The function pointer must declare two hidden parameters, self and _cmd.

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

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

Message forwarding and dynamic method resolution are mostly the same, and a class has the opportunity to dynamically resolve the method before the message is forwarded. If you have already call respondsToSelector: or instancesRespondToSelector: method, dynamic method resolution has a chance to add IMP priority for the Selector. If you implement the resolveInstanceMethod: method, but want a specific Selector to go through the message forwarding process, return this method to NO.

Forwarding and multiple inheritance

Multiple inheritance can be simulated using a message forwarding mechanism, such as the figure below, where the Warrior event is handled by another class even though there is no inheritance relationship between two classes.

As you can see from the above example, classes belonging to two inheritance branches implement the inheritance relationship through the message forwarding mechanism. The Warrior negotiate message is implemented by its “parent” Diplomat.

Multiple inheritance through message forwarding has an advantage over normal inheritance. Message forwarding can forward messages to multiple objects, so that code can be encapsulated into different objects with different responsibilities and processed by message forwarding to different objects.

Note that Warrior, while able to respond to the Negotiate message through the message forwarding mechanism, still returns NO if judged by the respondsToSelector: and isKindOfClass: methods. If you want these methods to return YES when judging the negotiate method, you need to rewrite them and add judgment logic.

- (BOOL)respondsToSelector:(SEL)aSelector {
    if ([super respondsToSelector:aSelector]) {
        return YES;
    } else {
        // 
    }
    return NO;
}
Copy the code

Before perform forwardInvocation: need through methodSignatureForSelector: method returns the method signature, if you don’t use the default method signature.

During method signing, unimplemented methods can be forwarded to their agents.

- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector {
    NSMethodSignature* signature = [super methodSignatureForSelector:selector];
    if(! signature) { signature = [surrogate methodSignatureForSelector:selector]; }return signature;
}
Copy the code

Runtime type encoding

Use skills

It is common in projects to crash programs by calling unimplemented methods. After learning about message forwarding, you can solve this problem through message forwarding.

All classes are based on the NSObject class (except NSProxy), and you can intercept the message forwarding flow of the NSObject class and then do some uniform processing to resolve the crash caused by the unimplemented method. Based on the fact that a Category can “override” the original class method, you can implement the corresponding interception method in a Category.

// Custom class
#import <Foundation/Foundation.h>
@interface TestObject : NSObject
- (void)testMethod;
@end

// Receive message IMP
void dynamicResolveMethod(id self, SEL _cmd) {
    NSLog(@"method forward");
}

// Category created for NSObject
@implementation NSObject (ExceptionForward)

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    const char *types = sel_getName(sel);
    class_addMethod([self class], sel, (IMP)dynamicResolveMethod, types);
    return YES;
}

#pragma clang diagnostic pop

@end
Copy the code

Our interception scheme is to dynamically create unimplemented methods in the resolveInstanceMethod method and set IMP to dynamicResolveMethod function for processing. In this way, all unimplemented methods will execute dynamicResolveMethod instead of crashing. You can perform crash statistics in dynamicResolveMethod.

Multiple inheritance

Multiple inheritance is not supported in OC, but can be simulated through message forwarding. Instantiate multiple parent classes in a subclass, and when a message is sent, the call is redirected to the instance object of the parent class in the message forwarding method to achieve the effect of multiple inheritance.

Here is an example of multiple inheritance. Create two parent classes Cat and Dog and define all methods that require subclass inheritance into Protocol. Implement the methods in Protocol in Cat and Dog.

@protocol CatProtocol <NSObject>
- (void)eatFish;
@end

@interface Cat : NSObject <CatProtocol>
@end

@implementation Cat
- (void)eatFish {
    NSLog(@"Cat Eat Fish");
}
@end

@protocol DogProtocol <NSObject>
- (void)eatBone;
@end

@interface Dog : NSObject
@end

@implementation Dog
- (void)eatBone {
    NSLog(@"Dog Eat Bone");
}
@end
Copy the code

Subclasses directly indicate which classes they “inherit” from and internally instantiate the corresponding superclass objects by obeying the parent class’s protocol. When a protocol method is called, the subclass does not actually implement the methods of the parent class, so the message is forwarded to the parent class of the response using the forward method.

@interface TestObject : NSObject <CatProtocol.DogProtocol>
@end

@interface TestObject(a)
@property (nonatomic.strong) Cat *cat;
@property (nonatomic.strong) Dog *dog;
@end

@implementation TestObject

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if ([self.cat respondsToSelector:aSelector]) {
        return self.cat;
    } else if ([self.dog respondsToSelector:aSelector]) {
        return self.dog;
    } else {
        return self; }}// Ignore the initialization of Cat and Dog
@end
Copy the code

Due to typesetting problems, the reading experience is not good, such as layout, picture display, code and many other problems. So go to Github and download the Runtime PDF collection. Put all the Runtime articles together in this PDF, with a table of contents on the left for easy reading.

Please give me a thumbs up, thank you! 😁