forward

Message forwarding is a feature of The Objective-C messaging mechanism. There are not many scenarios used in actual projects, but it is important to understand the mechanism. OC’s messaging mechanism allows a user to have the opportunity to respond to a message (SEL) without implementing the concrete method (IMP) of the message. It can be understood as a supplement to sending messages, and is dedicated to handling the case when a message is not found. Therefore, ordinary sending messages, if the corresponding method is not found in the list of methods of the instance object’s class (or the metaclass of the class object), will enter the message forwarding process, which consists of the following steps.

Dynamic method parsing

With resolveClassMethod resolveInstanceMethod: :

These two methods allow developers to dynamically add concrete implementations of methods.

static void sayInstanceName(id self, SEL cmd, id value) {
    NSLog(@"resolveInstanceMethod %@", value);
}

static void sayClassName(id self, SEL cmd, id value) {
    NSLog(@"resolveClassMethod %@", value);
}
Copy the code
@implementation Person + (BOOL)resolveInstanceMethod:(SEL)sel { NSLog(@"resolveInstanceMethod"); NSString *methodName = NSStringFromSelector(sel); if ([methodName isEqualToString:@"sayInstanceName:"]) { class_addMethod([self class], sel, (IMP)sayInstanceName, "v@:@"); return YES; } else if ([methodName isEqualToString:@"dynamicName"]) { class_addMethod(self, sel, (IMP)myDynamicName, "@@:"); return YES; } return [super resolveInstanceMethod:sel]; } // Note: when // self is an instance object, both [self class] and object_getClass(self) return classes (the former calls the latter), and object_getClass([self class]) returns metaclass. // when self is a class object, [self class] returns itself, and object_getClass(self) returns a metaclass, equivalent to object_getClass([self]). + (BOOL)resolveClassMethod:(SEL)sel { NSString *methodName = NSStringFromSelector(sel); if ([methodName isEqualToString:@"sayClassName:"]) { class_addMethod(object_getClass(self), sel, (IMP)sayClassName, "v@:@"); return YES; } return [super resolveClassMethod:sel]; } @endCopy the code

Instance methods and class methods can be set.

Note that there is a Type Encoding involved in the method implementation, so let’s look at the simple correspondence for now.

[Target][action][parameter]Copy the code

// V @:@, i.e

// v void
// @ id
// : SEL
// @ id
Copy the code

forwardingTargetForSelector

Message forwarding, which forwards a message to an object that can respond to the message, either as a property of the instance object or as an unrelated object.

/ / support only forward the message to an object - (id) forwardingTargetForSelector: (SEL) aSelector {NSLog (@ "forwardingTargetForSelector"); // All attributes of the object can be queried to see who can respond to the message, Return if ([NSStringFromSelector(aSelector) isEqualToString:@"sayInstanceName:"]) {if ([self.helper respondsToSelector:aSelector]) { return self.helper; } // Do not execute, So forwardingTargetForSelector support only forward the message to an object if ([self. AnotherHelper respondsToSelector: aSelector]) {return self.anotherHelper; } } return [super forwardingTargetForSelector:aSelector]; }Copy the code

Note: Only one object can forward the message here.

forwardInvocation

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSLog(@"methodSignatureForSelector"); if ([self.helper respondsToSelector:aSelector]) { return [self.helper methodSignatureForSelector:aSelector]; } if ([self.anotherHelper respondsToSelector:aSelector]) { return [self.anotherHelper methodSignatureForSelector:aSelector]; } return [super methodSignatureForSelector:aSelector]; } // Support for forwarding messages to any number of objects, - (void)forwardInvocation:(NSInvocation *)anInvocation {NSLog(@"forwardInvocation"); SEL sel = anInvocation.selector; if ([self.helper respondsToSelector:sel]) { [anInvocation invokeWithTarget:self.helper]; } if ([self.anotherHelper respondsToSelector:sel]) { [anInvocation invokeWithTarget:self.anotherHelper]; }}Copy the code

Here, the signature of the method is strictly matched, and then the corresponding message forwarding is performed.

doesNotRecognizeSelector

If the message cannot be sent after all the above steps have been performed, the following method will be called:

// unrecognized selector sent to instance
- (void)doesNotRecognizeSelector:(SEL)aSelector
{
    NSLog(@"doesNotRecognizeSelector");
}
Copy the code

NSObject’s default implementation of this method throws an exception such as:

[Person sayClassName:]: unrecognized selector sent to class 0x109344950
Copy the code

Difference between forwardSelector and NSInvocation

In general, the differences between the two phases are as follows:

  • ForwardSelector can forward a message attempt to only one object.
  • The forwardInvocation can forward messages to multiple objects.

In-depth understanding of NSInvocation

NSMethodSignature

The method signature contains the method name, parameters, and return values. In iOS, use Type Encoding.

  1. target
  2. selector
  3. arguments
  4. return value

For example:

- (void)myVoid:(NSDictionary *)params {
    NSLog(@"myFunc %@", params);
}

- (BOOL)myBool:(NSDictionary *)params {
    NSLog(@"myFunc %@", params);
    return YES;
}

- (NSInteger)myNSInteger:(NSDictionary *)params {
    NSLog(@"myFunc %@", params);
    return 1;
}

- (CGFloat)myCGFloat:(NSDictionary *)params {
    NSLog(@"myFunc %@", params);
    return CGFLOAT_MAX;
}

- (id)myFunc:(NSDictionary *)params {
    NSLog(@"myFunc %@", params);
    return [params copy];
}
Copy the code
- (void)testInvocations {
    SEL myVoid = @selector(myVoid:);
    SEL myBool = @selector(myBool:);
    SEL myNSInteger = @selector(myNSInteger:);
    SEL myCGFloat = @selector(myCGFloat:);
    SEL myFunc = @selector(myFunc:);
    NSDictionary *params = @{@"name": @"name"};
    NSMethodSignature *myVoidSig = [self methodSignatureForSelector:myVoid]; // v@:@
    NSMethodSignature *sigMyBool = [self methodSignatureForSelector:myBool]; // B@:@
    NSMethodSignature *sigMyNSInteger = [self methodSignatureForSelector:myNSInteger]; // q@:@
    NSMethodSignature *sigMyCGFloat = [self methodSignatureForSelector:myCGFloat]; // d@:@
    NSMethodSignature *sigMyFunc = [self methodSignatureForSelector:myFunc]; // @@:@
    NSLog(@"testInvocations");
}
Copy the code

Again, the format for Type Encoding is:

[Target][action][parameter]Copy the code

The corresponding representations of each type are void-v, Bool -b, NSInteger-q, CGFlat-d, and ID -@ respectively.

For example, myFunc: is denoted as @@:@, that is, the return value is ID, and the receiving parameter is also of type NSObject (NSDictonary).

NSInvocation

NSInvocation can be used to send messages to any OC object and there are fixed steps for its use:

  1. Initializes the method signature object NSMethodSignature according to selector
  2. According to the method signature NSMethodSignature object to initialize the NSInvocation object, must use invocationWithMethodSignature: method.
  3. Set target and selector.
  4. Set the parameters, and note that the index of the parameters starts at 2, because 0 and 1 correspond to target and selector, respectively. An error occurs if the index parameter is exceeded.
  5. Invoke the INVOKE method of the NSInvocation object.
  6. If there is a return value, use the getReturnValue of the NSInvocation object to get the return value.
- (void)testInvocation { SEL sel = @selector(myFunc:); NSDictionary *params = @{@"name": @"name"}; NSMethodSignature *sig = [self methodSignatureForSelector:sel]; if (! sig) { return; } const char *retType = [sig methodReturnType]; if (strcmp(retType, @encode(void)) == 0) { NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig]; invocation.target = self; invocation.selector = sel; [invocation setArgument:&params atIndex:2]; [invocation invoke]; NSLog(@"void ret"); /// 0 is target, 1 is action. The argument starts at 2. void *target; SEL action; [invocation getArgument:&target atIndex:0]; [invocation getArgument:&action atIndex:1]; /// target-action : <AppDelegate: 0x600003d69080>-myFunc: NSLog(@"target-action : %@-%@", target, NSStringFromSelector(action)); } else if (strcmp(retType, @encode(NSInteger)) == 0) { NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig]; invocation.target = self; invocation.selector = sel; [invocation setArgument:&params atIndex:2]; [invocation invoke]; NSInteger ret = 0; [invocation getReturnValue:&ret]; NSLog(@"NSInteger ret %ld", (long)ret); } else if (strcmp(retType, @encode(NSUInteger)) == 0) { NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig]; invocation.target = self; invocation.selector = sel; [invocation setArgument:&params atIndex:2]; [invocation invoke]; NSUInteger ret = 0; [invocation getReturnValue:&ret]; NSLog(@"NSUInteger ret %ld", (long)ret); } else if (strcmp(retType, @encode(BOOL)) == 0) { NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig]; invocation.target = self; invocation.selector = sel; [invocation setArgument:&params atIndex:2]; [invocation invoke]; BOOL ret = false; [invocation getReturnValue:&ret]; NSLog(@"BOOL ret %ld", (long)ret); } else if (strcmp(retType, @encode(CGFloat)) == 0) { NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig]; invocation.target = self; invocation.selector = sel; [invocation setArgument:&params atIndex:2]; [invocation invoke]; CGFloat ret = 0; [invocation getReturnValue:&ret]; NSLog(@"CGFloat ret %ld", (long)ret); } else {/// performSelector is strict, if the return values do not match, it is likely to crash. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" id ret = [self performSelector:sel withObject:params]; #pragma clang diagnostic pop NSLog(@"id ret %@", ret); }}Copy the code

retainArguments

Note that the NSInvocation is not strongly referenced by default, so if the parameter is released before the NSInvocation invocation is executed it will cause a wild pointer exception (EXC_BAD_ACCESS).

If there are scenarios where strong references to parameters are required, such as delayed execution of the Invoke method, a strong-hold operation is required on the parameters. Call retainArguments, and an attribute argumentsRetained.

Take a look at the detailed description in the developer documentation and notice the details: The object type is a retain operation, while c-string and blocks are copy operations.

Instance Method
retainArguments
If the receiver hasn’t already done so, retains the target and all object arguments of the receiver and copies all of its C-string arguments and blocks. If a returnvalue has been set, this is also retained or copied.

Declaration
- (void)retainArguments;

Discussion
Before this method is invoked, argumentsRetained returns NO; after, it returns YES.

For efficiency, newly created NSInvocation objects don’t retain or copy their arguments, nor do they retain their targets, copy C strings, or copy any associated blocks. You should instruct an NSInvocation object to retain its arguments if you intend to cache it, because the arguments may otherwise be released before the invocation is invoked. NSTimer objects always instruct their invocations to retain their arguments, forExample, because there's usually a delay before a timer fires.Copy the code

getReturnValue

The getReturnValue method simply copies the returned data to the specified memory area, regardless of memory management. If the returned object type is __unsafe_unretained. Optimization methods include:

Manually adding a strong hold will automatically add the autoRelease keyword to the returned object, with no wild pointer exception.

NSObject __unsafe_unretained *tmpRet;
[invoke getReturnValue:&tmpRet];
NSObject *ret = tmpRet;
return ret;
Copy the code

Alternatively, use __bridge for type conversion, which is more recommended.

void *tmpRet = NULL;
[invoke getReturnValue:&tmpRet];
NSObject *ret = (__bridge NSObject *)tmpRet;
return ret;
Copy the code

Application scenarios of message forwarding

Dynamic properties

If you use the @dynamic keyword on a property, the compiler does not automatically generate getter/setter methods for it, but rather dynamically lookup methods.

So, if you use the @dynamic keyword without manually adding getter/setter methods, you will get an error using it.

@property (nonatomic, copy) NSString *dynamicName; . @implementation Person @dynamic dynamicName; . @endCopy the code

You can add getters/setters dynamically using the resolveInstanceMethod: method.

Multiple inheritance

The object orientation of OC is a single inheritance relationship. If there are isolated situations where multiple inheritance is required, use forwardInvocation:. But don’t do it unless you have to.

/ / is used to describe is forwarded message - (NSMethodSignature *) methodSignatureForSelector: (SEL) aSelector {NSLog (@ "methodSignatureForSelector"); id p1 = [[NSClassFromString(@"BasePerson1") alloc] init]; if ([p1 respondsToSelector:aSelector]) { return [p1 methodSignatureForSelector:aSelector]; } id p2 = [[NSClassFromString(@"BasePerson2") alloc] init]; if ([p2 respondsToSelector:aSelector]) { return [p2 methodSignatureForSelector:aSelector]; } return [super methodSignatureForSelector:aSelector]; } - (void)forwardInvocation:(NSInvocation *)anInvocation { NSLog(@"forwardInvocation"); SEL sel = anInvocation.selector; id p1 = [[NSClassFromString(@"BasePerson1") alloc] init]; if ([p1 respondsToSelector:sel]) { [anInvocation invokeWithTarget:p1]; } id p2 = [[NSClassFromString(@"BasePerson2") alloc] init]; if ([p2 respondsToSelector:sel]) { [anInvocation invokeWithTarget:p2]; }}Copy the code

The resources

  • Use THE NSInvocation to send messages to objects
  • NSMethodSignature