The conventional process

Now that you’ve looked at the class infrastructure, let’s focus on message forwarding. Define a class RLObject that inherits from NSObject, and define a method -testInstanceMethod. Here’s how it works:

@interface RLObject : NSObject
- (void)testInstanceMethod;
@end
Copy the code
@implementation RLObject
- (void)testInstanceMethod {
    NSLog(@"call normal method");
}
@end
Copy the code

Create the RLObject object in main.m and call the -testInstancemethod method:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        RLObject *obj = [[RLObject alloc] init];
        [obj testInstanceMethod];
    }
    return 0;
}
Copy the code

The console will print the following information:

call normal method
Copy the code

If you comment out the implementation of the -testInstancemethod method in rlobject. m and run the program again, the program will crash and the console will print a classic error message:

-[RLObject testInstanceMethod]: unrecognized selector sent to instance 0x10070aac0
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[RLObject testInstanceMethod]: unrecognized selector sent to instance 0x10070aac0'
Copy the code

But there are three more opportunities to save your application before throwing an exception:

  • Method Resolution
  • Fast Forwarding
  • Normal Forwarding

Message forwarding mechanism

Method Resolution

First, if the object method does not exist, runtime calls the +resolveInstanceMethod: method; If the class method does not exist, the Runtime calls the +resolveClassMethod: method (last example). Both methods are class methods, and returning YES means that an instance method (or class method) can be added to handle the nonexistent method, in effect associating the method with an implementation of a C function. Return NO and proceed to the next step of message forwarding, noting that the last two steps are for object methods only.

Void instanceMethodIMP(id, SEL) +resolveInstanceMethod;

void instanceMethodIMP(id self, SEL _cmd) {
    NSLog(@"call instanceMethodIMP");
}
Copy the code
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(testInstanceMethod)) {
        class_addMethod([self class], sel, (IMP)instanceMethodIMP, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
Copy the code

Run the program again, no exception is thrown, and print the following information:

call instanceMethodIMP
Copy the code

Fast Forwarding

In this step the runtime by calling – forwardingTargetForSelector: method to ask whether there is stock objects to deal with this message, if have, this method returns can process the message object; And if you return nil it’s going to go on to the next step. This approach can be applied to simulate multiple inheritance by internally handing an unknown method to an object that implements the method, while externally looking as if it handled the method itself.

Add a BackupObject class that implements the testInstanceMethod method and comment out the +resolveInstanceMethod: method in rlobject. m. New – forwardingTargetForSelector: methods:

@interface BackupObject : NSObject
- (void)testInstanceMethod;
@end
Copy the code
@implementation BackupObject
- (void)testInstanceMethod {
    NSLog(@"call fast forwarding");
}
@end
Copy the code
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(testInstanceMethod)) {
        return [[BackupObject alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}
Copy the code

Run the program again and the console will print the following information:

call fast forwarding
Copy the code

Normal Forwarding

This step is essentially the same as the previous step: switch the object receiving the message, the previous step only needs to return a responsive object, and this step requires manually switching the response method to the standby response object. This step the runtime first call – methodSignatureForSelector: the signature of the method to get the function (in fact, the function parameters and return values of the type of information encapsulation), if the returns nil program crash directly, Throws the previous unrecognized selector sent to instance… The exception; If a signature is returned, the Runtime creates an NSInvocation object and calls the -ForwardInvocation: method.

We commented RLObject. M – forwardingTargetForSelector: in the method, and implement methods – methodSignatureForSelector: and – forwardInvocation:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
    if(! signature) { signature = [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return signature;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    BackupObject *backupObj = [[BackupObject alloc] init];
    SEL sel = anInvocation.selector;
    if ([backupObj respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:backupObj];
    } else{[selfdoesNotRecognizeSelector:sel]; }}Copy the code

summary

The entire message forwarding process can be summarized as follows:

At each step the object has a chance to process the unknown message, and the further it goes, the more expensive it becomes. The best thing to do is to do it in the first step, because at this point the Runtime caches the method in the cache and does not trigger the message forwarding process when it encounters the method again. Step 2 is a simplification of Step 3, just provide a standby message handling object, and step 3 also needs to create and handle an NSInvocation.

supplement

An unknown class method is similar to an object method except that it throws a slightly different exception. We ignore normal flow and call a nonexistent class method +testClassMethod in mian. M

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [RLObject performSelector:@selector(testClassMethod)];
    }
    return 0;
}
Copy the code

The console displays the following error message:

+[RLObject testClassMethod]: unrecognized selector sent to class 0x100003ae8
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[RLObject testClassMethod]: unrecognized selector sent to class 0x100003ae8'
Copy the code

Void classMethodIMP(id, SEL) is added to rlobject. m, and the method +resolveClassMethod is implemented:

void classMethodIMP(id self, SEL _cmd) {
    NSLog(@"call classMethodIMP");
}
Copy the code
+ (BOOL)resolveClassMethod:(SEL)sel {
    if (sel == @selector(testClassMethod)) {
        class_addMethod(objc_getMetaClass(class_getName([self class])), sel, (IMP)classMethodIMP, "v@:");
        return YES;
    }
    return [super resolveClassMethod:sel];
}
Copy the code

One thing to note here is the class object callclassThe method is not availablemeta classthe

reference

  • Objective-C Runtime Programming Guide
  • Understand objective-C Runtime mechanics in depth
  • Mechanism and usage of the iOS Runtime message forwarding mechanism
  • Effective Objective-C 2.0