Objective-C: вызов селекторов с несколькими аргументами

143

В MyClass.m я определил

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

и соответствующее объявление в MyClass.h. Позже я хочу позвонить

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

в MyClass.m, но я получаю сообщение об ошибке, похожее на * Завершение работы приложения из-за неперехваченного исключения 'NSInvalidArgumentException', причина: '* - [MyClass myTest: withAtring:]: нераспознанный селектор отправлен в экземпляр 0xe421f0'

Я попробовал более простой случай с селектором, который не принимал аргументов, выводил строку на консоль и работал нормально. Что не так с кодом и как это исправить? Спасибо.

Стю
источник
4
В вашем сообщении спрашивается о «нескольких аргументах», но вы используете только один. Теперь мне любопытно, как кто-то БУДЕТ делать это с несколькими аргументами, кроме как заключать их в массив / dict / что угодно.
RonLugge

Ответы:

137

Подпись вашего метода:

- (void) myTest:(NSString *)

withAString является параметром (имя вводит в заблуждение, похоже, что это часть сигнатуры селектора).

Если вы вызываете функцию таким образом:

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

Это будет работать.

Но, как предлагали другие плакаты, вы можете переименовать метод:

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

И звоните:

[self performSelector:@selector(myTestWithAString:) withObject:myString];
Линдси Фергюсон
источник
2
Теперь, когда я вижу, что люди получили пользу от этого ответа, я просмотрел свой ответ; Я бы посоветовал использовать простой вызов: - (void) testWithString: (NSString *) aString;
Lyndsey Ferguson
314

В Objective-C сигнатура селектора состоит из:

  1. Имя метода (в данном случае myTest) (обязательно)
  2. ':' (Двоеточие) после имени метода, если метод имеет ввод.
  3. Имя и ':' для каждого дополнительного ввода.

Селекторы не знают:

  1. Типы ввода
  2. Тип возвращаемого значения метода.

Вот реализация класса, в которой метод performMethodsViaSelectors выполняет другие методы класса посредством селекторов:

@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

Метод, для которого вы хотите создать селектор, имеет один вход, поэтому вы должны создать селектор для него следующим образом:

SEL myTestSelector = @selector(myTest:);
Шейн Арни
источник
3
Хороший ответ. Чтобы немного уточнить, имя селектора ДОЛЖНО иметь по крайней мере одну часть, которая может принимать или не принимать параметр - если это так, оно должно иметь двоеточие. Имена селекторов с двумя или более частями ДОЛЖНЫ иметь двоеточие после КАЖДОЙ части - недопустимо иметь селектор в форме «-useFoo: andBar: toDoSomething».
Куинн Тейлор,
Спасибо за это. Я какое-то время боролась с этим, рада помощи!
Джеймс Холл
как насчет входных параметров целые числа? что делать в этом случае?
Hoang Pham
1
Вам нужно обернуть целое число в объекте NSNumber (см developer.apple.com/library/ios/#documentation/Cocoa/Reference/... ), и получить целое значение в теле вызываемого метода. Это может быть немного многословно (и я не нашел лучшего способа обойти это), но работает нормально.
Шейн Арни,
30
+100: Это круто! Я не знал, что могу использовать несколько параметров withObject :. Я бы проголосовал за это сотню раз, если бы мог ...
FreeAsInBeer
13

@Shane Arney

performSelector:withObject:withObject:

Вы также можете упомянуть, что этот метод предназначен только для передачи максимум 2 аргументов и его нельзя отложить. (например performSelector:withObject:afterDelay:).

как-то странно, что яблоко поддерживает отправку только двух объектов и не делает его более общим.

Лирик
источник
2
Спасибо за информацию. Я не мог заставить задержку работать, и теперь я знаю почему. К вашему сведению, чтобы обойти ограничение в два объекта, я передал массив, а затем использовал его в методе.
JScarry
7

У вашего кода две проблемы. Один был идентифицирован и ответил, а другой - нет. Во-первых, в вашем селекторе отсутствовало имя параметра. Однако даже когда вы это исправите, строка все равно вызовет исключение, если ваша измененная сигнатура метода все еще включает более одного аргумента. Допустим, ваш измененный метод объявлен как:

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

Создание селекторов для методов, которые принимают несколько аргументов, совершенно допустимо (например, @selector (myTestWithString: compareTo :)). Однако метод performSelector позволяет передавать в myTest только одно значение, которое, к сожалению, имеет более одного параметра. Он выдаст ошибку и сообщит, что вы не предоставили достаточно значений.

Вы всегда можете переопределить свой метод, чтобы получить коллекцию как единственный параметр:

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

Однако есть более элегантное решение (не требующее рефакторинга). Ответ заключается в использовании NSInvocation, наряду с его setArgument:atIndex:и invokeметодами.

Я написал статью, включая пример кода , если вам нужны подробности. Основное внимание уделяется многопоточности, но основы все еще применимы.

Удачи!

Зак
источник
3

Сигнатура вашего метода не имеет смысла, вы уверены, что это не опечатка? Я не понимаю, как он вообще компилируется, хотя, возможно, вы получаете предупреждения, которые игнорируете?

Сколько параметров вы ожидаете от этого метода?

Роб Напье
источник
Извините, что вы пишете. Я напечатал его и попытался упростить вместо того, чтобы копировать и вставлять свой код, но в процессе я допустил ошибку. Я ожидаю, что этот метод примет один параметр; строка, которую я хочу напечатать.
Stu
2

Думаю, класс следует определить как:

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

У вас есть только один параметр, поэтому у вас должен быть только один:

Возможно, вы захотите также рассмотреть возможность использования% @ в вашем NSLog - это просто хорошая привычка - затем будет записывать любой объект, а не только строки.

Grouchal
источник
-1

Пользователи iOS также ожидают автокапитализации: в стандартном текстовом поле первая буква предложения на языке с учетом регистра автоматически становится заглавной.

Вы можете решить, реализовывать ли такие функции; нет специального API для какой-либо из перечисленных функций, поэтому их предоставление является конкурентным преимуществом.

В документе Apple говорится, что для этой функции и некоторых других ожидаемых функций на настраиваемой клавиатуре нет API. поэтому вам нужно выяснить свою собственную логику, чтобы реализовать это.

Каннан Прасад
источник