148

In MyClass.m, I've defined

- (void) myTest: (NSString *) withAString{
    NSLog(@"hi, %@", withAString);
}

and the appropriate declaration in MyClass.h . Later I want to call

[self performSelector:@selector(mytest:withAString:) withObject: mystring];

in MyClass.m but I get an error similar to * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* -[MyClass myTest:withAtring:]: unrecognized selector sent to instance 0xe421f0'

I tried a simpler case with a selector that took no arguments that printed a string to console and that worked just fine. What's wrong with the code and how can I fix it? Thanks.

Stu
  • 2,679
  • 4
  • 26
  • 25
  • 4
    Your post is asking about 'multiple arguments', but you only use one. Now I'm curious about how someone WOULD do it with multiple arguments, other than wrapping them up in an array / dict / whatever. – RonLugge Mar 12 '12 at 00:24

8 Answers8

316

In Objective-C, a selector's signature consists of:

  1. The name of the method (in this case it would be 'myTest') (required)
  2. A ':' (colon) following the method name if the method has an input.
  3. A name and ':' for every additional input.

Selectors have no knowledge of:

  1. The input types
  2. The method's return type.

Here's a class implementation where performMethodsViaSelectors method performs the other class methods by way of selectors:

@implementation ClassForSelectors
- (void) fooNoInputs {
    NSLog(@"Does nothing");
}
- (void) fooOneInput:(NSString*) first {
    NSLog(@"Logs %@", first);
}
- (void) fooFirstInput:(NSString*) first secondInput:(NSString*) second {
    NSLog(@"Logs %@ then %@", first, second);
}
- (void) performMethodsViaSelectors {
    [self performSelector:@selector(fooNoInputs)];
    [self performSelector:@selector(fooOneInput:) withObject:@"first"];
    [self performSelector:@selector(fooFirstInput:secondInput:) withObject:@"first" withObject:@"second"];
}
@end

The method you want to create a selector for has a single input, so you would create a selector for it like so:

SEL myTestSelector = @selector(myTest:);
Shane Arney
  • 4,314
  • 3
  • 22
  • 17
  • 3
    Good answer. To clarify slightly, you the selector name MUST have at least one part, which may or may not take a parameter — if it does, it must have a colon. Selector names with two or more parts MUST have a colon after EACH part — it is not legal to have a selector of the form "-useFoo:andBar:toDoSomething". – Quinn Taylor Jun 19 '09 at 18:00
  • thanks for this. ive been struggling with this for a while, glad for the help! – James Hall Aug 17 '09 at 15:22
  • how about the input parameters are integer numbers? what to do in this case? – Hoang Pham Sep 23 '10 at 17:47
  • 1
    You'll need to wrap the integer in an NSNumber object (see http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSNumber_Class/Reference/Reference.html), and retrieve the integer value in the body of the called method. It can be a bit verbose (and I haven't found a better way around it) but it works fine. – Shane Arney Sep 24 '10 at 13:44
  • 30
    +100: This is awesome! I didn't know about being able to use multiple "withObject:" parameters. I would upvote this a hundred times if I could... – FreeAsInBeer Mar 31 '11 at 19:06
  • If you need to send more than two parameters, you can use id objc_msgSend(id theReceiver, SEL theSelector, ...) or look up NSInvocation. – Winter Dragoness Jan 25 '15 at 16:28
  • Great answer, I did not know about multiple parameters, I always had to wrap everything inside a single object. This should be the accepted answer, according to the question title. – afe Apr 10 '17 at 07:34
138

Your method signature is:

- (void) myTest:(NSString *)

withAString happens to be the parameter (the name is misleading, it looks like it is part of the selector's signature).

If you call the function in this manner:

[self performSelector:@selector(myTest:) withObject:myString];

It will work.

But, as the other posters have suggested, you may want to rename the method:

- (void)myTestWithAString:(NSString*)aString;

And call:

[self performSelector:@selector(myTestWithAString:) withObject:myString];
Mark Coleman
  • 40,542
  • 9
  • 81
  • 101
Lyndsey Ferguson
  • 5,306
  • 1
  • 29
  • 46
  • 2
    Now that I see people have benefited from this answer, I have reviewed my response; I would suggest that the call be simply: -(void)testWithString:(NSString*)aString; – Lyndsey Ferguson Mar 12 '12 at 12:46
14

@Shane Arney

performSelector:withObject:withObject:

You might also want to mention that this method is only for passing maximum 2 arguments, and it cannot be delayed. (such as performSelector:withObject:afterDelay:).

kinda weird that apple only supports 2 objects to be send and didnt make it more generic.

Lirik
  • 3,167
  • 1
  • 30
  • 31
  • 2
    Thanks for the info. I couldn’t get the delay to work and now I know why. FYI, to get around the limit of two objects, I passed an array and then used it in the method. – JScarry Jan 28 '15 at 21:09
7

Your code has two problems. One was identified and answered, but the other wasn't. The first was that your selector was missing the name of its parameter. However, even when you fix that, the line will still raise an exception, assuming your revised method signature still includes more than one argument. Let's say your revised method is declared as:

-(void)myTestWithString:(NSString *)sourceString comparedTo:(NSString *)testString ;

Creating selectors for methods that take multiple arguments is perfectly valid (e.g. @selector(myTestWithString:comparedTo:) ). However, the performSelector method only allows you to pass one value to myTest, which unfortunately has more than one parameter. It will error out and tell you that you didn't supply enough values.

You could always redefine your method to take a collection as it's only parameter:

-(void)myTestWithObjects:(NSDictionary *)testObjects ;

However, there is a more elegant solution (that doesn't require refactoring). The answer is to use NSInvocation, along with its setArgument:atIndex: and invoke methods.

I've written up an article, including a code example, if you want more details. The focus is on threading, but the basics still apply.

Good luck!

Zack
  • 1,256
  • 9
  • 20
3

Your method signature makes no sense, are you sure it isn't a typo? I'm not clear how it's even compiling, though perhaps you're getting warnings that you're ignoring?

How many parameters do you expect this method to take?

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • Sorry you are write. I typed it out and tried to make it simpler instead of copy and pasting my code but I made a mistake in the process. I'm expecting this method to take one parameter; the string I would like to print. – Stu Jun 19 '09 at 14:28
2

Think the class should be defined as:

- (void) myTestWithSomeString:(NSString *) astring{
    NSLog(@"hi, %s", astring);
}

You only have a single parameter so you should only have a single :

You might want to consider using %@ in your NSLog also - it is just a good habit to get into - will then write out any object - not just strings.

Grouchal
  • 9,756
  • 6
  • 34
  • 46
0

Post at 2021_10_15 by an android developer.

(OC is harder to use, -_-||)

Calling selectors with multiple arguments,

you can use NSObject performSelector:withObject:withObject,

but only support pass two arguments!!!

Fortunately, you can implement you performSelector withObject X 3 by objc_msgSend function.

#include <objc/message.h>

- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2 withObject:(id)object3 {
    typedef id (*send_type)(id, SEL, id, id, id);
    send_type func = (send_type) objc_msgSend;
    id retValue = func(self, aSelector, object1, object2, object3);
    return retValue;
}

Usage:

- (NSString *)ObjcMsgSendWithString:(NSString *)string withNum:(NSNumber *)number withArray:(NSArray *)array {
    NSLog(@" ---> %@, %@, %@", string, number, array[0]);
    return @"return 311";
}

- (void)test{
    NSString *str = @"字符串objc_msgSend";
    NSNumber *num = @20;
    NSArray *arr = @[@"数组值1", @"数组值2"];

    SEL sel = @selector(ObjcMsgSendWithString:withNum:withArray:);
    NSLog(@"1223 ---> %@", [self performSelector:sel withObject:str withObject:num withObject:arr]);
}
hansey li
  • 17
  • 4
-1

iOS users also expect autocapitalization: In a standard text field, the first letter of a sentence in a case-sensitive language is automatically capitalized.

You can decide whether or not to implement such features; there is no dedicated API for any of the features just listed, so providing them is a competitive advantage.

Apple document is saying there is no API available for this feature and some other expected feature in a customkeyboard. so you need to find out your own logic to implement this.

Kannan Prasad
  • 1,796
  • 22
  • 27