views:

11929

answers:

5

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.

+2  A: 

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
+2  A: 

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
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
+7  A: 

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];
Lyndsey Ferguson
Thanks! Minor correction. The "t" in "mytest:" should be capitalized to "T".self performSelector:@selector(mytest:) withObject:myString];
Stu
Thanks, updated my post :)
Lyndsey Ferguson
+17  A: 

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) fooOneIput:(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
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
thanks for this. ive been struggling with this for a while, glad for the help!
James Hall
how about the input parameters are integer numbers? what to do in this case?
sfa
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
+2  A: 

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