Utilisation de -performSelector: vs.appeler simplement la méthode

116

Je suis encore un peu nouveau sur Objective-C et je me demande quelle est la différence entre les deux déclarations suivantes?

[object performSelector:@selector(doSomething)]; 

[object doSomething];
Le joueur
la source

Réponses:

191

Fondamentalement, performSelector vous permet de déterminer dynamiquement quel sélecteur appeler un sélecteur sur l'objet donné. En d'autres termes, le sélecteur n'a pas besoin d'être déterminé avant l'exécution.

Ainsi, même si ceux-ci sont équivalents:

[anObject aMethod]; 
[anObject performSelector:@selector(aMethod)]; 

Le deuxième formulaire vous permet de faire ceci:

SEL aSelector = findTheAppropriateSelectorForTheCurrentSituation();
[anObject performSelector: aSelector];

avant d'envoyer le message.

ennuikiller
la source
3
Il vaut la peine de souligner que vous assigneriez en fait le résultat de findTheAppropriateSelectorForTheCurrentSituation () à aSelector, puis invoqueriez [anObject performSelector: aSelector]. @selector produit un SEL.
Daniel Yankowsky le
4
L'utilisation performSelector:est quelque chose que vous ne faites probablement que si vous implémentez l'action cible dans votre classe. Les frères performSelectorInBackground:withObject:et sœurs performSelectorOnMainThread:withObject:waitUntilDone:sont souvent plus utiles. Pour générer un thread d'arrière-plan et pour rappeler les résultats au thread principal à partir dudit thread d'arrière-plan.
PeyloW
2
performSelectorest également utile pour supprimer les avertissements de compilation. Si vous savez que la méthode existe (comme après l'utilisation respondsToSelector), cela empêchera Xcode de dire "peut ne pas répondre à your_selector". Ne l'utilisez pas au lieu de découvrir la véritable cause de l'avertissement. ;)
Marc
J'ai lu sur un autre fil sur StackOverflow que l'utilisation de performSelector était le reflet d'un design horrible, et il avait des tonnes de pouces vers le haut. J'aimerais pouvoir le retrouver. J'ai cherché sur Google, en limitant les résultats à stackoverflow, et j'ai obtenu 18 000 résultats. Eww.
Logicsaurus Rex
"reflet d'un design horrible" est trop simpliste. C'était ce que nous avions avant que les blocs ne soient disponibles, et toutes les utilisations ne sont pas mauvaises, à l'époque ou maintenant. Bien que maintenant que les blocs soient disponibles, c'est probablement un meilleur choix pour le nouveau code à moins que vous ne fassiez quelque chose de très simple.
Ethan
16

Pour cet exemple très basique dans la question,

[object doSomething];
[object performSelector:@selector(doSomething)]; 

il n'y a aucune différence dans ce qui va se passer. doSomething sera exécuté de manière synchrone par objet. Seul "doSomething" est une méthode très simple, qui ne renvoie rien et ne nécessite aucun paramètre.

était-ce quelque chose d'un peu plus compliqué, comme:

(void)doSomethingWithMyAge:(NSUInteger)age;

les choses se compliqueraient, parce que [object doSomethingWithMyAge: 42];

ne peut plus être appelée avec aucune variante de "performSelector", car toutes les variantes avec paramètres n'acceptent que les paramètres d'objet.

Le sélecteur ici serait "doSomethingWithMyAge:" mais toute tentative de

[object performSelector:@selector(doSomethingWithMyAge:) withObject:42];  

ne compile tout simplement pas. passer un NSNumber: @ (42) au lieu de 42, n'aiderait pas non plus, car la méthode attend un type C de base - pas un objet.

De plus, il existe des variantes performSelector jusqu'à 2 paramètres, pas plus. Alors que les méthodes ont souvent beaucoup plus de paramètres.

J'ai découvert que bien que des variantes synchrones de performSelector:

- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

toujours retourner un objet, j'ai pu renvoyer un simple BOOL ou NSUInteger aussi, et cela a fonctionné.

L'une des deux principales utilisations de performSelector est de composer dynamiquement le nom de la méthode que vous souhaitez exécuter, comme expliqué dans une réponse précédente. Par exemple

 SEL method = NSSelectorFromString([NSString stringWithFormat:@"doSomethingWithMy%@:", @"Age");
[object performSelector:method];

L'autre utilisation est d'envoyer de manière asynchrone un message à l'objet, qui sera exécuté plus tard sur la boucle d'exécution actuelle. Pour cela, il existe plusieurs autres variantes de performSelector.

- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;
- (void)performSelector:(SEL)aSelector target:(id)target argument:(id)arg order:(NSUInteger)order modes:(NSArray *)modes;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg;

(oui, je les ai rassemblés à partir de plusieurs catégories de classes Foundation, comme NSThread, NSRunLoop et NSObject)

Chacune des variantes a son propre comportement spécial, mais toutes partagent quelque chose en commun (au moins lorsque waitUntilDone est défini sur NO). L'appel "performSelector" reviendrait immédiatement, et le message à l'objet ne sera placé sur la boucle d'exécution actuelle qu'après un certain temps.

En raison de l'exécution retardée - naturellement aucune valeur de retour n'est disponible dans la méthode du sélecteur, d'où la valeur de retour - (void) dans toutes ces variantes asynchrones.

J'espère avoir couvert cela d'une manière ou d'une autre ...

Motti Shneor
la source
12

@ennuikiller est parfait. Fondamentalement, les sélecteurs générés dynamiquement sont utiles lorsque vous ne connaissez pas (et ne pouvez généralement pas) connaître le nom de la méthode que vous appellerez lorsque vous compilerez le code.

Une différence clé est que -performSelector:et les amis (y compris les variantes multi-thread et retardé ) sont quelque peu limités en ce sens qu'ils sont conçus pour être utilisés avec des méthodes avec des paramètres 0-2. Par exemple, appeler -outlineView:toolTipForCell:rect:tableColumn:item:mouseLocation:avec 6 paramètres et renvoyer le NSStringest assez compliqué et n'est pas pris en charge par les méthodes fournies.

Quinn Taylor
la source
5
Pour ce faire, vous devez utiliser un NSInvocationobjet.
Dave DeLong le
6
Autre différence: performSelector:et les amis prennent tous des arguments d'objet, ce qui signifie que vous ne pouvez pas les utiliser pour appeler (par exemple) setAlphaValue:, car son argument est un flottant.
Chuck le
4

Les sélecteurs sont un peu comme des pointeurs de fonction dans d'autres langues. Vous les utilisez lorsque vous ne savez pas au moment de la compilation quelle méthode vous souhaitez appeler au moment de l'exécution. De plus, comme les pointeurs de fonction, ils encapsulent uniquement la partie verbale de l'invocation. Si la méthode a des paramètres, vous devrez également les transmettre.

An NSInvocationsert un objectif similaire, sauf qu'il rassemble plus d'informations. Non seulement il inclut la partie verbe, mais il inclut également l'objet cible et les paramètres. Ceci est utile lorsque vous souhaitez appeler une méthode sur un objet particulier avec des paramètres particuliers, pas maintenant mais dans le futur. Vous pouvez créer un fichier approprié NSInvocationet le déclencher plus tard.

Daniel Yankowsky
la source
5
Les sélecteurs ne ressemblent pas du tout à un pointeur de fonction dans la mesure où un pointeur de fonction est quelque chose que vous pouvez appeler avec des arguments et un sélecteur peut être utilisé pour appeler une méthode particulière sur n'importe quel objet qui l'implémente; un sélecteur n'a pas le contexte complet d'appel comme un pointeur de fonction.
bbum
1
Les sélecteurs ne sont pas les mêmes que les pointeurs de fonction, mais je pense toujours qu'ils sont similaires. Ils représentent des verbes. Les pointeurs de fonction C représentent également des verbes. Ni l'un ni l'autre n'est utile sans contexte supplémentaire. Les sélecteurs nécessitent un objet et des paramètres; les pointeurs de fonction nécessitent des paramètres (qui peuvent inclure un objet sur lequel opérer). Mon point était de souligner en quoi ils sont différents des objets NSInvocation, qui contiennent tout le contexte nécessaire. Ma comparaison était peut-être déroutante, auquel cas je m'excuse.
Daniel Yankowsky
1
Les sélecteurs ne sont pas des pointeurs de fonction. Pas même proche. Ce sont en réalité de simples chaînes C, qui contiennent un «nom» de méthode (par opposition à «fonction»). Ce ne sont même pas des signatures de méthode, car elles n'intègrent pas les types de paramètres. Un objet peut avoir plusieurs méthodes pour le même sélecteur (différents types de paramètres ou différents types de retour).
Motti Shneor
-7

Il y a une autre différence subtile entre les deux.

    [object doSomething]; // is executed right away

    [object performSelector:@selector(doSomething)]; // gets executed at the next runloop

Voici l'extrait de la documentation Apple

"performSelector: withObject: afterDelay: exécute le sélecteur spécifié sur le thread actuel lors du prochain cycle de boucle d'exécution et après une période de délai facultative. Comme il attend le prochain cycle de boucle d'exécution pour exécuter le sélecteur, ces méthodes fournissent un mini-délai automatique de le code en cours d'exécution. Plusieurs sélecteurs en file d'attente sont exécutés l'un après l'autre dans l'ordre dans lequel ils ont été mis en file d'attente. "

avi
la source
1
Votre réponse est factuellement incorrecte. La documentation que vous citez concerne performSelector:withObject:afterDelay:, mais la question et votre extrait de code utilisent performSelector:, ce qui est une méthode entièrement différente. À partir de la documentation pour cela: <quote> La performSelector:méthode équivaut à envoyer un aSelectormessage directement au destinataire. </quote>
jscs
3
merci Josh pour la clarification. Vous avez raison; Je pensais que performSelector/performSelector:withObject/performSelector:withObject:afterDelaytous se comportaient de la même manière, ce qui était une erreur.
avi