Stu
Stu

Reputation: 2699

Objective-C: Calling selectors with multiple arguments

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.

Upvotes: 150

Views: 223364

Answers (9)

Matheus Garcia
Matheus Garcia

Reputation: 39

The correct syntax is

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

I see a comment here regarding how one can't pass arguments other than objects, as well as how Apple limited to two. You can use the methodForSelector method to bypass these limitations.

SEL sel = @selector(mytest:);

(*[self methodForSelector: sel])(self, sel, mystring);

Dereferencing is optional. Therefore,

[self methodForSelector: sel](self, sel, mystring);

is valid as well.

Upvotes: 0

Shane Arney
Shane Arney

Reputation: 4344

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:);

Upvotes: 316

hansey li
hansey li

Reputation: 15

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]);
}

Upvotes: 0

Kannan Prasad
Kannan Prasad

Reputation: 1806

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.

Upvotes: -1

Lirik
Lirik

Reputation: 3187

@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.

Upvotes: 14

Lyndsey Ferguson
Lyndsey Ferguson

Reputation: 5384

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];

Upvotes: 138

Zack
Zack

Reputation: 1254

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!

Upvotes: 7

Rob Napier
Rob Napier

Reputation: 299355

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?

Upvotes: 3

Grouchal
Grouchal

Reputation: 9796

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.

Upvotes: 2

Related Questions