I’m also a newbie learning Runtime from the ground up, so some things might not be right. And this brief book typesetting is not how, there is a problem you can point out directly, do not leave the emotion.

There are a few questions about message forwarding, and I think it’s more efficient to look for answers with questions.

1. How is message forwarding triggered?

2. Are message forwarding divided into several steps?

3. What is the function of message forwarding?

Think about these three questions first, and then take a look at what’s going on in the Runtime with some questions.

1. How is message forwarding triggered?

Book.h declares a method – (void)sell; But the method is not implemented. Method definition for ‘sell’ not found

// // book. h // message forwarding // // Created by -- on 2019/8/2. // Copyright © 2019 --. All rights reserved#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Book : NSObject

- (void)sell;

@end

NS_ASSUME_NONNULL_END
Copy the code
// // book. m // message forwarding // // Created by -- on 2019/8/2. // Copyright © 2019 --. All rights reserved#import "Book.h"
#import <objc/runtime.h>

@implementation Book

//Method definition for 'sell' not found

@end
Copy the code

Then call [book Sell] in viewController.m’s viewDidLoad, which will obviously crash.

// // viewController. m // message forwarding // // Created by -- on 2019/8/1. // Copyright © 2019 --. All rights reserved#import "ViewController.h"
#import "Book.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //Class Book
    
    Book *book = [Book new];
    [book sell];
  
}

@end
Copy the code

The console then prints the following:

2019-08-02 09:57:059674 +0800 message forwarding [1205:43257] -[Book Sell]: unrecognized selector sent to instance 0x600002ff0fc[1205:43257] *** Terminating app due to uncaught exception'NSInvalidArgumentException', reason: '-[Book sell]: unrecognized selector sent to instance 0x600002ff0fc0'*** First throw call stack: ( 0 CoreFoundation 0x000000010e5888db __exceptionPreprocess + 331 1 libobjc.A.dylib 0x000000010db2bac5 objc_exception_throw + 48 2 CoreFoundation 0x000000010e5a6c94 -[NSObject(NSObject) doesNotRecognizeSelector:] + 132 3 CoreFoundation 0x000000010e58d623 ___forwarding___ + 1443 4 CoreFoundation 0x000000010e58f418 _CF_forwarding_prep_0 + 120 5 E ∂ aEA ø E ω ¨ Aee 0x000000010D25571A -[ViewController viewDidLoad] + 106Copy the code

The console prints out a crashed stack where the method implementation is not found, but what is interesting is that the _CF_forwarding_prep_0 and ___forwarding___ method calls occur after [ViewController viewDidLoad].

After searching the Internet, I found an article explaining objective-C message sending and forwarding mechanism (2).

**__CF_forwarding_prep_0 and forwarding_prep_1 both call forwarding with different arguments. Forwarding takes two parameters. The first parameter is the stack pointer to the message to be forwarded (simply known as IMP), and the second parameter marks whether a structure is returned. __CF_forwarding_prep_0 passes 0 as the second argument, and forwarding_prep_1 passes 1, as can be seen from the function name. Here is the pseudocode for these two functions:

[book sell]
Runtime
Method implementation
forward
-[Book sell]: unrecognized selector sent to instance 0x600002ff0fc0'

thenRuntimeHow did you find a way to do that?

The Runtime has a diagram like this that we need to keep in mind. Figure is as follows:

Superclass
isa
Runtime
Book
sell
Book
doesNotRecognizeSelector
Runtime

Now that I knowRuntimeIs the search way diagram, that specific search way?

Runtime ObjC2.0 ObjC2.0 Runtime ObjC2.0 ObjC2.0

struct objc_object { private: isa_t isa; . }Copy the code
struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        returnbits.data(); }... }Copy the code

We found what we were looking for under class_rw_t method_array_t.

struct class_rw_t { // Be warned that Symbolication knows the layout of this structure. uint32_t flags; uint32_t version; const class_ro_t *ro; method_array_t methods; property_array_t properties; protocol_array_t protocols; . }Copy the code
Struct method_t {// SEL name; // Return type const char *types; // Method implementation pointer address MethodListIMP IMP; };Copy the code

We looked at method_t stored in method_array_t and that’s where our method is stored in our class, based on the diagram above, along the parent class, and finally because Book and its parent class don’t have – (void)sell; Method implementation crashes and prints an exception message.

####2. Are message forwarding divided into several steps? Understand the bottom layer of the Runtime source code above, have a preliminary understanding of method lookup. Notice a particularly interesting method __forwarding__, and then look at the related OC methods in nsobject. h.

- (id) forwardingTargetForSelector: (SEL) aSelector OBJC_AVAILABLE (10.5, 2.0, 9.0, 1.0, 2.0); - (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE("");
Copy the code

Discover that these two methods seem to be related to __forwarding__. We can try. Does it matter?

  • So let’s break

The first breakpoint is entered, indicating that this is a step in message forwarding. After we looked at the official documentation, – (id) forwardingTargetForSelector: (SEL) return to first of all should be will not be able to identify the message aSelector directed to the object. That is, we need a new object that implements -(void) Sell to receive the message. BookStore. H has no declared methods. BookStore. M implements the – (void)sell method

// // BookStore. H // message forwarding // // Created by -- on 2019/8/2. // Copyright © 2019 --. All rights reserved#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface BookStore : NSObject

@end

NS_ASSUME_NONNULL_END

Copy the code
// // BookStore. M // message forwarding // // Created by -- on 2019/8/2. // Copyright © 2019 --. All rights reserved#import "BookStore.h"

@implementation BookStore

- (void)sell{
    NSLog(@"Book sale.");
}

@end
Copy the code

We rewrite the – (id) forwardingTargetForSelector (SEL) aSelector method

- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSString *selName = NSStringFromSelector(aSelector);
    if ([selName isEqualToString:@"sell"]) {
        return [BookStore new];
    }
    return [super forwardingTargetForSelector:aSelector];
}
Copy the code

Look at the console print. This step is correct.

[4032:301841] The book store is selling booksCopy the code
  • Let’s comment out the implementation

And then -[Book sell]: Unrecognized selector sent to instance 0x6000032dc6d0 same error, did – (void)forwardInvocation:(NSInvocation *)anInvocatio; Isn’t it a step in message forwarding?

Notice that there is an NSInvocation object in the forwardInvocation method.

@interface NSInvocation : NSObject {
@private
    void *_frame;
    void *_retdata;
    id _signature;
    id _container;
    uint8_t _retainedArgs;
    uint8_t _reserved[15];
}

+ (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig;

@property (readonly, retain) NSMethodSignature *methodSignature;
Copy the code

Then point in the found a + (NSInvocation *) invocationWithMethodSignature (sig NSMethodSignature *); The NSInvocation method requires a method signature.

+ (nullable NSMethodSignature *)signatureWithObjCTypes:(const char *)types; This is the method that generates the method signature.

When you look at the methods of nsobject.h, Also see – (void)forwardInvocation:(NSInvocation *)anInvocation has a – (NSMethodSignature) below *)methodSignatureForSelector:(SEL)aSelector; This appears to be an implementation of the method signature generated and returned to the Runtime. Try this:

const char *types
The official documentation
here
const char *types

Book.m starts with a method implementation equivalent to OC- (void)sell.

void sell(id self, SEL _cmd){
     NSLog(@"Book has sold himself.");
}
Copy the code

Const char *types @”v@:”

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSString *methodName = NSStringFromSelector(aSelector);
    if ([methodName isEqualToString:@"sell"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    SEL sel = [anInvocation selector];
    BookStore *bookStore = [BookStore new];
    if ([bookStore respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:bookStore];
    } else{/ / go inheritance tree [super forwardInvocation: anInvocation]; }}Copy the code

You can see that the message was forwarded successfully.

[4032:301841] The bookstore is selling booksCopy the code

So here’s a question. Runtime all the work happens at Runtime, so can you add methods dynamically at Runtime? Nsobject. h: + (BOOL)resolveInstanceMethod:(SEL) SEL; This looks like the dynamic parsing instance method.

Overwrite the change method, and then break.

This seems to be the method we need, so what should be implemented in this method? Check out the official document resolveInstanceMethod

This is too clear, copy ~ hahaha. Book.m is implemented as follows:

void sell(id self, SEL _cmd){
     NSLog(@"Book has sold himself.");
}

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    NSString *methodName = NSStringFromSelector(sel);
    if ([methodName isEqualToString:@"sell"]) {
        class_addMethod(self, sel, (IMP)sell, "v@:");
        returnYES; } // Go to the inheritance treereturn [super resolveInstanceMethod:sel];
}
Copy the code

Running results:

[6926:541823] Book sells itself ~Copy the code

After discovering the message forwarding realization mode of OC step by step, the sequence of message forwarding is shown as follows:

1, dynamic message forwarding resolveInstanceMethod:, dynamically add a method implementation; 2, fast forwarding forwardingTargetForSelector: news, forwarded to an implementation of the methods of class object; 3, complete message forwarding, first to obtain the method signature methodSignatureForSelector: then in forwardInvocation: set forward in the object.

  • The complete implementation code is as follows:
#import "Book.h"
#import <objc/runtime.h>
#import "BookStore.h"

void sell(id self, SEL _cmd){
     NSLog(@"Book has sold himself.");
}

@implementation Book

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    NSString *methodName = NSStringFromSelector(sel);
    if ([methodName isEqualToString:@"sell"]) {
        class_addMethod(self, sel, (IMP)sell, "v@:");
        returnYES; } // Go to the inheritance treereturn [super resolveInstanceMethod:sel];
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSString *methodName = NSStringFromSelector(aSelector);
    if ([methodName isEqualToString:@"sell"]) {
        return [BookStore new];
    }
    return [super forwardingTargetForSelector:aSelector];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSString *methodName = NSStringFromSelector(aSelector);
    if ([methodName isEqualToString:@"sell"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    SEL sel = [anInvocation selector];
    BookStore *bookStore = [BookStore new];
    if ([bookStore respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:bookStore];
    } else{/ / go inheritance tree [super forwardInvocation: anInvocation]; } } - (void)doesNotRecognizeSelector:(SEL)aSelector{ NSLog(@"Could not find a way to implement: %@",NSStringFromSelector(aSelector));
}

@end
Copy the code

3. The role of message forwarding

1> crash log collection 2> enhanced application robustness 3> implementation of multiple agents

Message forwarding mechanism can be used to implement multiple proxies without code intrusion, so that different objects can simultaneously broker the same callback, and then handle the corresponding processing in their respective areas of responsibility, reducing the degree of code coupling.

Blog.csdn.net/kingjxust/a…

Conclusion: Runtime slowly began to study, this is the first article of Runtime, I try my best to write things without mistakes, but there are always mistakes in learning, welcome to point out any problems, thank you guys.