Objective-C: Appel de sélecteurs avec plusieurs arguments

142

Dans MyClass.m, j'ai défini

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

et la déclaration appropriée dans MyClass.h. Plus tard, je veux appeler

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

dans MyClass.m mais j'obtiens une erreur similaire à * Terminer l'application en raison d'une exception non interceptée 'NSInvalidArgumentException', raison: '* - [MyClass myTest: withAtring:]: sélecteur non reconnu envoyé à l'instance 0xe421f0'

J'ai essayé un cas plus simple avec un sélecteur qui ne prenait aucun argument qui imprimait une chaîne sur la console et qui fonctionnait très bien. Quel est le problème avec le code et comment puis-je le résoudre? Merci.

Stu
la source
4
Votre message pose des questions sur les «arguments multiples», mais vous n'en utilisez qu'un. Maintenant, je suis curieux de savoir comment quelqu'un le ferait avec plusieurs arguments, autre que de les envelopper dans un tableau / dict / peu importe.
RonLugge

Réponses:

137

La signature de votre méthode est:

- (void) myTest:(NSString *)

withAString se trouve être le paramètre (le nom est trompeur, il semble qu'il fasse partie de la signature du sélecteur).

Si vous appelez la fonction de cette manière:

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

Ça va marcher.

Mais, comme l'ont suggéré les autres affiches, vous voudrez peut-être renommer la méthode:

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

Et appelez:

[self performSelector:@selector(myTestWithAString:) withObject:myString];
Lyndsey Ferguson
la source
2
Maintenant que je vois que les gens ont bénéficié de cette réponse, j'ai passé en revue ma réponse; Je suggérerais que l'appel soit simplement: - (void) testWithString: (NSString *) aString;
Lyndsey Ferguson
313

En Objective-C, la signature d'un sélecteur se compose de:

  1. Le nom de la méthode (dans ce cas, ce serait 'myTest') (obligatoire)
  2. Un ':' (deux-points) après le nom de la méthode si la méthode a une entrée.
  3. Un nom et «:» pour chaque entrée supplémentaire.

Les sélecteurs n'ont aucune connaissance de:

  1. Les types d'entrée
  2. Le type de retour de la méthode.

Voici une implémentation de classe où la méthode performMethodsViaSelectors effectue les autres méthodes de classe au moyen de sélecteurs:

@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

La méthode pour laquelle vous souhaitez créer un sélecteur a une seule entrée, vous devez donc créer un sélecteur pour elle comme suit:

SEL myTestSelector = @selector(myTest:);
Shane Arney
la source
3
Bonne réponse. Pour clarifier légèrement, le nom du sélecteur DOIT avoir au moins une partie, qui peut ou non prendre un paramètre - si c'est le cas, il doit avoir un signe deux-points. Les noms de sélecteur avec deux parties ou plus DOIVENT avoir un deux-points après CHAQUE partie - il n'est pas légal d'avoir un sélecteur de la forme "-useFoo: andBar: toDoSomething".
Quinn Taylor
Merci pour cela. ive se débat avec cela depuis un moment, heureux de l'aide!
James Hall
que diriez-vous des paramètres d'entrée sont des nombres entiers? que faire dans ce cas?
Hoang Pham
1
Vous devrez envelopper l'entier dans un objet NSNumber (voir developer.apple.com/library/ios/#documentation/Cocoa/Reference/… ) et récupérer la valeur entière dans le corps de la méthode appelée. Cela peut être un peu verbeux (et je n'ai pas trouvé de meilleur moyen de contourner cela) mais cela fonctionne bien.
Shane Arney
30
+100: C'est génial! Je ne savais pas comment utiliser plusieurs paramètres "withObject:". Je voterais cent fois si je pouvais ...
FreeAsInBeer
13

@Shane Arney

performSelector:withObject:withObject:

Vous voudrez peut-être également mentionner que cette méthode sert uniquement à transmettre un maximum de 2 arguments et qu'elle ne peut pas être retardée. (comme performSelector:withObject:afterDelay:).

un peu bizarre qu'Apple ne supporte que 2 objets à envoyer et ne le rend pas plus générique.

Lirik
la source
2
Merci pour l'info. Je n'ai pas pu faire fonctionner le retard et maintenant je sais pourquoi. Pour info, pour contourner la limite de deux objets, j'ai passé un tableau et l'ai ensuite utilisé dans la méthode.
JScarry
7

Votre code a deux problèmes. L'un a été identifié et répondu, mais pas l'autre. La première était que votre sélecteur manquait le nom de son paramètre. Cependant, même lorsque vous corrigez cela, la ligne lèvera toujours une exception, en supposant que votre signature de méthode révisée inclut toujours plus d'un argument. Disons que votre méthode révisée est déclarée comme:

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

Créer des sélecteurs pour des méthodes qui acceptent plusieurs arguments est parfaitement valide (par exemple @selector (myTestWithString: compareTo :)). Cependant, la méthode performSelector ne vous permet de passer qu'une seule valeur à myTest, qui a malheureusement plus d'un paramètre. Cela provoquera une erreur et vous indiquera que vous n'avez pas fourni suffisamment de valeurs.

Vous pouvez toujours redéfinir votre méthode pour prendre une collection car ce n'est qu'un paramètre:

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

Cependant, il existe une solution plus élégante (qui ne nécessite pas de refactorisation). La réponse est d'utiliser NSInvocation, ainsi que ses méthodes setArgument:atIndex:et invoke.

J'ai rédigé un article, y compris un exemple de code , si vous voulez plus de détails. L'accent est mis sur le filetage, mais les bases s'appliquent toujours.

Bonne chance!

Zack
la source
3

Votre signature de méthode n'a aucun sens, êtes-vous sûr que ce n'est pas une faute de frappe? Je ne sais pas comment il est même compilé, même si vous recevez peut-être des avertissements que vous ignorez?

Combien de paramètres pensez-vous que cette méthode prendra?

Rob Napier
la source
Désolé, vous écrivez. Je l'ai tapé et j'ai essayé de le rendre plus simple au lieu de copier et coller mon code mais j'ai fait une erreur dans le processus. Je m'attends à ce que cette méthode prenne un paramètre; la chaîne que je voudrais imprimer.
Stu
2

Pensez que la classe devrait être définie comme:

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

Vous n'avez qu'un seul paramètre, vous ne devriez donc en avoir qu'un seul:

Vous pouvez également envisager d'utiliser% @ dans votre NSLog - c'est juste une bonne habitude à prendre - qui écrira alors n'importe quel objet - pas seulement des chaînes.

Grouchal
la source
-1

Les utilisateurs iOS s'attendent également à une autocapitalisation: dans un champ de texte standard, la première lettre d'une phrase dans une langue sensible à la casse est automatiquement mise en majuscule.

Vous pouvez décider d'implémenter ou non ces fonctionnalités; il n'y a pas d'API dédiée pour l'une des fonctionnalités énumérées ci-dessus, donc leur fournir est un avantage concurrentiel.

Le document Apple indique qu'il n'y a pas d'API disponible pour cette fonctionnalité et une autre fonctionnalité attendue dans un clavier personnalisé. vous devez donc découvrir votre propre logique pour implémenter cela.

Kannan Prasad
la source