Objective-C: Chamando seletores com vários argumentos

142

No MyClass.m, eu defini

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

e a declaração apropriada em MyClass.h. Mais tarde eu quero ligar

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

em MyClass.m, mas recebo um erro semelhante a * Terminando o aplicativo devido à exceção não capturada 'NSInvalidArgumentException', motivo: '* - [MyClass myTest: withAtring:]: seletor não reconhecido enviado para a instância 0xe421f0'

Tentei um caso mais simples com um seletor que não aceitava argumentos que imprimissem uma string no console e que funcionasse bem. O que há de errado com o código e como posso corrigi-lo? Obrigado.

Stu
fonte
4
Sua postagem está perguntando sobre "vários argumentos", mas você usa apenas um. Agora estou curioso sobre como alguém faria isso com vários argumentos, além de agrupá-los em uma matriz / dict / qualquer coisa.
21712 RonLugge

Respostas:

137

Sua assinatura do método é:

- (void) myTest:(NSString *)

passa a ser o parâmetro withAString (o nome é enganoso, parece que faz parte da assinatura do seletor).

Se você chamar a função desta maneira:

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

Vai funcionar.

Mas, como os outros pôsteres sugeriram, você pode renomear o método:

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

E ligue para:

[self performSelector:@selector(myTestWithAString:) withObject:myString];
Lyndsey Ferguson
fonte
2
Agora que vejo que as pessoas se beneficiaram com essa resposta, revi minha resposta; Eu sugeriria que a chamada fosse simplesmente: - (void) testWithString: (NSString *) aString;
Lyndsey Ferguson
313

No Objective-C, a assinatura de um seletor consiste em:

  1. O nome do método (nesse caso, seria 'myTest') (obrigatório)
  2. Um ':' (dois pontos) após o nome do método, se o método tiver uma entrada.
  3. Um nome e ':' para cada entrada adicional.

Os seletores não têm conhecimento de:

  1. Os tipos de entrada
  2. O tipo de retorno do método.

Aqui está uma implementação de classe em que o método performMethodsViaSelectors executa os outros métodos de classe por meio de seletores:

@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

O método para o qual você deseja criar um seletor possui uma única entrada; portanto, você deve criar um seletor para o seguinte:

SEL myTestSelector = @selector(myTest:);
Shane Arney
fonte
3
Boa resposta. Para esclarecer um pouco, você o nome do seletor DEVE ter pelo menos uma parte, que pode ou não ter um parâmetro - se houver, deve ter dois pontos. Os nomes de seletores com duas ou mais partes DEVEM ter dois pontos após CADA parte - não é legal ter um seletor no formato "-useFoo: andBar: toDoSomething".
Quinn Taylor
obrigado por isso. Eu tenho lutado com isso por um tempo, feliz pela ajuda!
James Hall
e os parâmetros de entrada são números inteiros? o que fazer neste caso?
Hoang Pham
1
Você precisará agrupar o número inteiro em um objeto NSNumber (consulte developer.apple.com/library/ios/#documentation/Cocoa/Reference/… ) e recuperar o valor inteiro no corpo do método chamado. Pode ser um pouco detalhado (e não encontrei uma maneira melhor de contornar isso), mas funciona bem.
precisa saber é o seguinte
30
+100: Isso é incrível! Eu não sabia sobre a possibilidade de usar vários parâmetros "withObject:". Eu upvote esta uma centena de vezes se eu pudesse ...
FreeAsInBeer
13

@Shane Arney

performSelector:withObject:withObject:

Você também pode mencionar que esse método é apenas para passar no máximo 2 argumentos e não pode ser adiado. (como performSelector:withObject:afterDelay:).

meio estranho que a apple suporte apenas 2 objetos a serem enviados e não a torne mais genérica.

Lirik
fonte
2
Obrigado pela informação. Não consegui atrasar o trabalho e agora sei o porquê. Para sua informação, para contornar o limite de dois objetos, passei por uma matriz e depois a usei no método.
precisa saber é o seguinte
7

Seu código tem dois problemas. Um foi identificado e respondido, mas o outro não. A primeira foi a falta do nome do parâmetro no seu seletor. No entanto, mesmo quando você corrige isso, a linha ainda gera uma exceção, supondo que a assinatura do método revisado ainda inclua mais de um argumento. Digamos que seu método revisado seja declarado como:

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

Criar seletores para métodos que usam vários argumentos é perfeitamente válido (por exemplo, @selector (myTestWithString: compareTo :)). No entanto, o método performSelector apenas permite que você transmita um valor para myTest, que infelizmente possui mais de um parâmetro. Irá ocorrer um erro e informar que você não forneceu valores suficientes.

Você sempre pode redefinir seu método para obter uma coleção, pois é apenas um parâmetro:

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

No entanto, existe uma solução mais elegante (que não requer refatoração). A resposta é a utilização de NSInvocation, juntamente com os seus setArgument:atIndex:e invokemétodos.

Escrevi um artigo, incluindo um exemplo de código , se você quiser mais detalhes. O foco está na segmentação, mas o básico ainda se aplica.

Boa sorte!

Zack
fonte
3

Sua assinatura de método não faz sentido, você tem certeza de que não é um erro de digitação? Não sei ao certo como está compilando, embora talvez você esteja recebendo avisos que está ignorando?

Quantos parâmetros você espera que esse método tome?

Rob Napier
fonte
Desculpe, você está escrevendo. Digitei e tentei simplificá-lo em vez de copiar e colar meu código, mas cometi um erro no processo. Estou esperando que esse método use um parâmetro; a string que eu gostaria de imprimir.
Stu
2

Pense que a classe deve ser definida como:

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

Você só tem um único parâmetro, portanto, apenas um:

Você também pode considerar o uso de% @ no NSLog - é apenas um bom hábito - escreverá qualquer objeto - não apenas as strings.

Grouchal
fonte
-1

Os usuários do iOS também esperam a autocapitalização: em um campo de texto padrão, a primeira letra de uma frase em um idioma que diferencia maiúsculas de minúsculas é automaticamente maiúscula.

Você pode decidir se deve ou não implementar esses recursos; não há API dedicada para nenhum dos recursos listados, portanto, fornecê-los é uma vantagem competitiva.

O documento da Apple está dizendo que não há API disponível para esse recurso e algum outro recurso esperado em um teclado personalizado. então você precisa descobrir sua própria lógica para implementar isso.

Kannan Prasad
fonte