2

Just like UIButton where I can:

[button addTarget:target forSelector:@selector(action)]

Or:

[button addTarget:target forSelector:@selector(action:)]

Where in the second one the action for the button will have a sender (being the button itself), how do I determine if a given selector has parameter, so that I can pass something to it?

jscs
  • 63,694
  • 13
  • 151
  • 195
Heuristic
  • 5,087
  • 9
  • 54
  • 94

2 Answers2

3

As a first pass, I would use NSStringFromSelector(). If for some reason that gave you problems, I would use sel_getName.

NSInteger MYParameterCountOfSelector(SEL selector) {
    NSString *string = NSStringFromSelector(selector);
    return [string componentsSeparatedByString:@":"].count - 1;
}
Jeffery Thomas
  • 42,202
  • 8
  • 92
  • 117
2

Jeffery is right (and lots of dynamic ObjC is handled by messing with selectors this way rather than diving into the guts of NSMethodSignature, which gives you much more precise information). But equally, you can always pass something. I know that sounds crazy, but passing too many things is not actually a problem in ObjC.

@interface MyObject: NSObject
- (void)something;
@end

...

MyObject *object = [MyObject new];
[object performSelector:@selector(something) withObject:@"thing"];

Works fine. What!?!? Yeah. This is actually how your button example works. Put a breakpoint in your IBAction and go look at the caller:

0x108b21d2c <+58>: movq   0x1400a1d(%rip), %rsi     ; "performSelector:withObject:withObject:"
0x108b21d33 <+65>: movq   %rbx, %rdi
0x108b21d36 <+68>: movq   %r12, %rdx
0x108b21d39 <+71>: movq   %r15, %rcx
0x108b21d3c <+74>: movq   %r14, %r8
0x108b21d3f <+77>: callq  *0x10ac7f3(%rip)          ; (void *)0x0000000108019ac0: objc_msgSend

It always calls performSelector:withObject:withObject: and passes you the sender and the event even if you didn't ask for them. Try looking at $rdx (arg2) and $rcx (arg3) if you don't believe me (assuming you're in the Simulator. If you want all the right registers, you'll want the list). From my (IBAction)doThing method:

(lldb) po $rdx
<UIButton: 0x7f8071a126f0; frame = (164 318; 46 30); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x608000223140>>

(lldb) po $rcx
<UITouchesEvent: 0x6180001068a0> timestamp: 1.06651e+06 touches: {(
    <UITouch: 0x7f8070500e20> phase: Ended tap count: 1 force: 0.000 window: <UIWindow: 0x7f8071a10890; frame = (0 0; 414 736); autoresize = W+H; gestureRecognizers = <NSArray: 0x60800026dbc0>; layer = <UIWindowLayer: 0x608000222800>> view: <UIButton: 0x7f8071a126f0; frame = (164 318; 46 30); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x608000223140>> location in window: {205, 335} previous location in window: {205, 335} location in view: {41, 17} previous location in view: {41, 17}
)}

So yes, you can tear apart the selector and figure out what to send or not send, but the typical way to do this in Cocoa is just set things up so you can always send everything and let the receiver ignore it.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • 1
    This is is behavior of C. Is it "crazy"? – Amin Negm-Awad Jun 26 '17 at 05:13
  • @AminNegm-Awad Not crazy at all. Just likely surprising to those who aren't familiar with the low-level calling conventions of C-like languages (this is not universally true among languages). It's certainly true that this same approach can be (and is) used for indirectly calling C function pointers. – Rob Napier Jun 26 '17 at 14:20
  • @AminNegm-Awad: I don't think this is actually defined in C. I believe that trying to call a function with a function pointer of a different signature (i.e. calling a function with no parameters using a function pointer type with parameters) is technically undefined behavior in C. What they do here just happens to work on the Apple architectures (and probably most other architectures). – newacct Jul 04 '17 at 05:25
  • @newacct I did not check it with the standard. But the implementation does not have a prototype. In such a case, correct me if I'm wrong, passing too much args is not UB. This is, because it can only break, if the pass way of the first args to the functions params depends on the total number of params. But this cannot work at all, because without prototype the number of params are unknown. I think, the RTE simply takes advantages out of a defined behavior. But maybe I'm wrong. – Amin Negm-Awad Jul 04 '17 at 07:45
  • @AminNegm-Awad: I'm not sure what you mean by "the implementation does not have a prototype" – newacct Jul 09 '17 at 17:45
  • @newacct A method implementation is analogous to the function definition in pure C. IIRC, the C standard only states UB, if the calling code has a different number of args in comparision to a prototype of the called function. But we do not have a prototype (or an analogous method declaration) here. But maybe I'm wrong with this. – Amin Negm-Awad Jul 10 '17 at 11:13
  • @AminNegm-Awad: Every time you call a function, you must have a function type that you believe the function to be (if you are using a function pointer then it's the function pointer type) in order for the compiler to compile it. The standard says that you can cast between function pointer types but if you use a function pointer to call a function whose type is incompatible with the function pointer type, it is UB. The documentation of `-performSelector:withObject:` requires a certain type of function, as it says "aSelector should identify a method that takes a single argument of type id". – newacct Jul 13 '17 at 05:52
  • @AminNegm-Awad: This allows `-performSelector:withObject:` internally to perform a cast to the function type `id (*)(id, SEL, id)`, which is the function pointer type of the implementation of a method that takes one `id` and returns an `id`, and use that to make the call. If the method has a different set of parameter type(s) and/or return type, the the implementing function has a different type, and calling the implementing function through a function pointer of the above type is UB. – newacct Jul 13 '17 at 05:53
  • I do not think that you cannot compile a source containing a function call to a unknown function. IIRC it is possible. That's what I say: If the compiler does not know, what the called function accepts, you can still have such a function call and the call stack is prepared in a way the call works, if the call is "incidentally" correct. The standard does not forbid this. – Amin Negm-Awad Jul 13 '17 at 08:02
  • @newacct It is (solely) not casted to `id (*)(id, SEL, id)`, because this would obviously make it impossible to call a "function" with different args. You have to use a lib as libffi to do that. Solely casting dos not work for many reasons. (Try to write a piece of code that is able to pass a number of args decided on run time. In pure C this is not possible.) – Amin Negm-Awad Jul 13 '17 at 08:05
  • @AminNegm-Awad: I don't know what you mean by "solely casting". `-[NSObject performSelector:withObject:]` by its documentation is only allowed to be used for a selector to a method whose implementation is `id (*)(id, SEL, id)`. Similarly, `-[NSObject performSelector:]` is only allowed to be used for a selector to a method whose implementation is `id (*)(id, SEL)` and `-[NSObject performSelector:withObject:withObject:]` is only allowed to be used for a selector to a method whose implementation is `id (*)(id, SEL, id, id)`. – newacct Jul 19 '17 at 18:53
  • @AminNegm-Awad: [Here](https://opensource.apple.com/source/objc4/objc4-709/runtime/NSObject.mm.auto.html) is the source code for `NSObject`'s `performSelector:...` methods if you don't believe me. Each of them casts to the appropriate function pointer type for the respective implementing function type exactly as I described. They cast `objc_msgSend` because `objc_msgSend` is a trampoline that behaves like it has the type of the implementing function that is called. – newacct Jul 19 '17 at 18:56