Message forwarding mechanism

1. Dynamic method analysis

When an object receives a message that cannot be processed, it will call the following methods. The former will be called when calling class methods, and the latter will be called when calling object methods

//Class method call not implemented
+ (BOOL)resolveClassMethod:(SEL)sel
//Instance method call not implemented
+ (BOOL)resolveInstanceMethod:(SEL)sel

In this method, you need to dynamically add a method to the class to which the object belongs, and return YES, indicating that it can be processed

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    NSString *method = NSStringFromSelector(sel);
    if ([@"playPiano" isEqualToString:method]) {
        /**
         Add method

         @param self The object that calls the method
         @param sel Selector
         @param IMP The newly added method is implemented in c language
         @param The type of the newly added method, including the return value of the function and the content type of the parameter, eg: void xxx(NSString *name, int size). The type is: v@i
         */
        class_addMethod(self, sel, (IMP)playPiano, "v");
        return YES;
    }
    return NO;
}

The premise of using this method is that the relevant method code has been implemented, but the modified method is dynamically added to the target class at run time. For example, the @ dynamic attribute used in CoreData is to dynamically add method implementations at runtime.

2. Backup recipient

After the first step, if the message still cannot be processed, the following method will be called to query whether other objects can process the message.

- (id)forwardingTargetForSelector:(SEL)aSelector

In this method, we need to return an object that can process the message

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    NSString *seletorString = NSStringFromSelector(aSelector);
    if ([@"playPiano" isEqualToString:seletorString]) {
        Student *s = [[Student alloc] init];
        return s;
    }
    // Continue forwarding
    return [super forwardingTargetForSelector:aSelector];
}

3. Complete message forwarding

After the first two steps, if you still cannot process the message, you will make a final attempt. First call methodSignatureForSelector: to obtain the method signature, and then call forwardInvocation: for processing. The processing in this step can be directly forwarded to other objects, that is, the effect is equivalent to that in the second step, but few people do so, because the later the message processing is, the greater the cost of processing the message and the greater the performance overhead. Therefore, in this way, the message content will be changed, such as adding parameters, changing selectors, and so on.

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- (void)forwardInvocation:(NSInvocation *)anInvocation

The following is an example of changing the selector. For example, we directly called the playPiano method and finally forwarded it to the traval: method.

// Complete message forwarding
- (void)travel:(NSString*)city
{
    NSLog(@"Teacher travel: %@", city);
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSString *method = NSStringFromSelector(aSelector);
    if ([@"playPiano" isEqualToString:method]) {

        NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:@"];
        return signature;
    }
    return nil;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    SEL sel = @selector(travel:);
    NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:@"];
    anInvocation = [NSInvocation invocationWithMethodSignature:signature];
    [anInvocation setTarget:self];
    [anInvocation setSelector:@selector(travel:)];
    NSString *city = @"Beijing";
    // The first parameter of the message is self, and the second parameter is the selector, so "Beijing" is the third parameter
    [anInvocation setArgument:&city atIndex:2];

    if ([self respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:self];
        return;
    } else {
        Student *s = [[Student alloc] init];
        if ([s respondsToSelector:sel]) {
            [anInvocation invokeWithTarget:s];
            return;
        }
    }

    // Find from inheritance tree
    [super forwardInvocation:anInvocation];
}

+ (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);//Class method
 + (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);//Object method

 - (id)forwardingTargetForSelector:(SEL)aSelector OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);//Fast message forwarding

 //Forwarding of standard messages
 - (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE("");
 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");

Code example of message forwarding mechanism:

Create a new Person class for demonstration

Person.h

#import <Foundation/Foundation.h>

@interface Person : NSObject
- (void)run;
@end

Person.m

#import "Person.h"
#import <objc/message.h>

@implementation Person

// 1. Dynamic method
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@"sel = %@", NSStringFromSelector(sel));

    // 1. If it is judged that there is no implementation method, the method will be added dynamically
//    if (sel == @selector(run:)) {
//        //Dynamic addition method
//        class_addMethod(self, sel, (IMP)newRun, "v@:@");
//        return YES;
//    }

    //2. If the method of dynamic addition is not used, give it to super
    return [super resolveInstanceMethod:sel];
}

void newRun(id self,SEL sel, NSString *str) {
    NSLog(@"---run Are you up yet %@--",str);
}

// II Message forwarding redirection
- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"%@",NSStringFromSelector(aSelector));
    return [super forwardingTargetForSelector:aSelector];
//    return [[Animation alloc] init];
}


// III Generate method signature
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    // 1. Convert string
    NSString *sel = NSStringFromSelector(aSelector);

    // 2. Judge and manually generate signature
    if ([sel isEqualToString:@"run"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    } else {
    return [super methodSignatureForSelector:aSelector];
    }
}

// IV Get the method signature distribution message
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"-----%@",anInvocation);
    // 1. Get the news
    SEL selector = [anInvocation selector];
    // 2. Forward the message
    Animation *anm = [[Animation alloc] init];

    if ([anm respondsToSelector:selector]) {
        // Call this object to forward
        [anInvocation invokeWithTarget:anm];

    } else {
        [super forwardInvocation:anInvocation];
    }
}


// V Throw exception
- (void)doesNotRecognizeSelector:(SEL)aSelector {
    NSString *selStr = NSStringFromSelector(aSelector);
    NSLog(@"this-----%@---Method does not exist", selStr);
}

@end

Finally, I recommend my iOS communication group: 642363427 It's important to have a common circle and make friends! There are iOS development and full stack development. Welcome to settle in and make common progress! (the group will provide some free learning books and materials collected by the group owners and hundreds of interview questions and answer documents for free!)

Tags: iOS

Posted by hjunw on Fri, 20 May 2022 13:20:56 +0300