OC message forwarding mechanism

Keywords: Attribute C

OC method calls send messages, and if a method with the corresponding name cannot be found, there are three opportunities to remedy it before Crash occurs.

  1. Dynamic Method Analysis
  2. Standby Receiver
  3. Message forwarding

Dynamic Method Analysis

When an object receives an unknown message, it first calls the class method of its class + resolveInstanceMethod: (instance method) or + resolveClassMethod: (class method).
This method returns the Bool type, indicating whether the class can add a new method to handle this situation. Using this method, we need to write the corresponding method to implement the code, and add insertion at run time.

This method is often used to implement the setter getter method for the @dynamic attribute
e.g.

//
//  TestObjA.h
//  test
//
@interface TestObjA : NSObject
@property(nonatomic , strong) NSString *testStr;
@end

//
//  TestObjA.m
//  test
//
@implementation TestObjA
@dynamic testStr;
@end

In this case, crash occurs by calling setter and getter methods

    TestObjA *obj = [[TestObjA alloc] init];
    [obj setTestStr:@"haha"];
    NSString *result = [obj testStr];
    NSLog(result);

The following demonstrates how to implement the @dynamic attribute with this method

//
//  TestObjA.h
//  test
//
@interface TestObjA : NSObject
@property(nonatomic , strong) NSString *testStr;
@property (nonatomic, strong) NSString *storeStr;
@end

//
//  TestObjA.m
//  test
//
void dynamicStrSetter (id self,SEL _cmd, id value) {
    TestObjA *obj = (TestObjA *)self;
    obj.storeStr = (NSString *)value;
}

id dynamicStrGetter (id self,SEL _cmd) {
    TestObjA *obj = (TestObjA *)self;
    return obj.storeStr;
}


@implementation TestObjA
@dynamic testStr;

+ (BOOL)resolveInstanceMethod:(SEL)sel {

    if (sel == @selector(setTestStr:)) {
        class_addMethod(self, sel, (IMP)dynamicStrSetter, "v@:@");
        return true;
    }else if(sel == @selector(testStr)) {
        class_addMethod(self, sel, (IMP)dynamicStrGetter, "@@:");
        return true;
    }
    return [super resolveClassMethod:sel];
};
@end

In this way, when calling setter and getter methods, the C language method implemented before will be invoked to ensure the normal operation of the program.

Standby Receiver

If the previous step is not implemented, the following method will continue to be called

- (id)forwardingTargetForSelector:(SEL)aSelector

This method returns a non-empty object that acts as a new recipient of the method and sends the message again.

e.g.
Create a new TestObjB object

//
//  TestObjB.h
//  test
//
@interface TestObjB : NSObject
- (void)testFunc;
@end

//
//  TestObjB.m
//  test
//
@implementation TestObjB
- (void)testFunc {
    NSLog(@"test"); 
}
@end

Then implement the following method in TestObjA

- (id)forwardingTargetForSelector:(SEL)aSelector {
    return [[TestObjB alloc] init];
}

Now let's do another test:

     TestObjA *obj = [[TestObjA alloc] init];
    [obj performSelector:@selector(testFunc) withObject:nil];

Print output will be found: test

Message forwarding

Delete the code from Step 2 TestObjA above and add the code:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];

    if (!signature) {
        if ([TestObjB instancesRespondToSelector:aSelector]) {
            signature = [TestObjB instanceMethodSignatureForSelector:aSelector];
        }
    }
    return signature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    if ([TestObjB instancesRespondToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:[[TestObjB alloc] init]];
    }
}

Then run the last test code, and the results can still be printed out:test

At this stage, a method signature is generated by calling - (NSMethodSignature*) method Signature ForSelector:(SEL)aSelector

Then call the -(void) forward Invocation:(NSInvocation *) anInvocation method for message forwarding.

Note: Invocation initialization requires method signature: NSMethod Signature

So far, three steps have been completed to intercept an unknown message crash. If it is not processed, the forward Invocation: method of NSObject is called at runtime. The implementation of this method simply calls the doesNotRecognizeSelector: method, which does not forward any messages and throws exceptions directly.

Posted by Michael_C on Sun, 07 Jul 2019 13:05:57 -0700