Why creating NSInvocation has to specify selector twice
Asked Answered
V

3

6

Here is the example code I saw from Apple's "Timer Programming Topics":

NSMethodSignature *methodSignature = [self
methodSignatureForSelector:@selector(invocationMethod:)];
NSInvocation *invocation = [NSInvocation
invocationWithMethodSignature:methodSignature];
[invocation setTarget:self];
[invocation setSelector:@selector(invocationMethod:)];
NSDate *startDate = [NSDate date];
[invocation setArgument:&startDate atIndex:2];

as you can see, we have to specify the invocationMethod: once in the NSMethodSignature, and then second time in NSInvocation's setSelector. To me, this seems redundant, is there any reason why Apple design like this?

Vadose answered 15/12, 2014 at 9:30 Comment(0)
V
3

A selector is just a string, the name of the method. It contains no information about the types of parameters or return type. A method signature is just the types; it contains no information about the name of the method. They are completely disjoint.

Although in your case you create the method signature by asking the target object using methodSignatureForSelector:, you should not assume that people always want to do it that way.

You could construct a method signature directly from a string of type encodings. You could get it from the signature of another method and apply it to this method. Etc. It might not be appropriate, depending on the particular use case, to directly ask the object for the method signature, because maybe the object doesn't implement that method yet, and will dynamically add it later when called or something. There could be any of a number of reasons why it is useful to have the flexibility of specifying the types and method name separately.

Volk answered 16/12, 2014 at 1:37 Comment(0)
L
2

The selector is passed to two very different things.

First, we have:

NSMethodSignature *methodSignature = [self methodSignatureForSelector:@selector(invocationMethod:)];

This generates a method signature which describes the return value type as well as the number and types of arguments of a method. It does not remember which selector this signature was created for (the same signature is valid for multiple selectors; both -(void)foo:(NSString *)f; and -(void)bar:(NSString *)b; have the same signature even though their selectors/names are different).

Then you create the actual invocation. It needs to know the return value type as well as number and types of the arguments… so you initialize it with a NSMethodSignature. But it does not yet know which object and which selector it is supposed to call, you explicitly need to tell it. That's the second time the selector is passed.

You can also set the selector to a different one on the invocation which has the same signature and it will work as well.

Libradalibrarian answered 15/12, 2014 at 10:7 Comment(2)
thanks. I'm wondering, can't the code know information about "the return value type as well as the number and types of arguments of a method" when [invocation setSelector:@selector(invocationMethod:)]; is called? This way, we don't need to create a NSMethodSignature object ahead.Vadose
At first, I was thinking about nasty stuff like message forwarding, dynamic method resolution and other stuff that would prevent it but in fact I couldn't find an Objective-C feature that would explain why NSInvocation could not do what you just proposed. We can only guess about the reasons Apple/NeXT decided to implement it this way. But my gut feeling is that your proposal would slightly "taint" NSInvocation. Have a hard time explaining that, though.Libradalibrarian
G
1

The method signature lets the NSInvocation know the structure of the selector that the NSInvocation can use. Once you have create a NSInvocation you can't change the structure of the selector that the invocation uses, however you can change the selector. So one argument is used to work out the structure of the method used, and the other is the method that is actually called.

Later you can change the method that it actually called provided it has the same structure as the method signature you gave it in the first place. You could write a category with a convince constructor:

+ (instancetype)invocationWithTarget:(id)target andSelector:(SEL)selector {
  NSMethodSignature *methodSignature = [target methodSignatureForSelector:selector];
  NSInvocation *invocation = [self invocationWithMethodSignature:methodSignature];
  [invocation setTarget:target];
  [invocation setSelector:selector];
  return invocation;
}
Gristly answered 15/12, 2014 at 10:4 Comment(3)
A few problems: You specify return type instancetype but return an NSInvocation. Unless you call this on the NSInvocation class this is wrong. The name is methodSignatureWithTarget:andSelector: (BTW, you forgot the : after WithTarget) but it returns an invocation. So the name should be invocationWithTarget:andSelector:.Libradalibrarian
@Libradalibrarian Thanks, that'll teach me to answer in a rush (obviously the category should on NSInvocation).Gristly
I imagine even cooler would be a category method on NSObject, invocationWithSelector:.Libradalibrarian

© 2022 - 2024 — McMap. All rights reserved.