performSelector may cause a leak because its selector is unknown

Je suis tombé sur un problème intéressant, autant le partager avec vous.



Je suis en train d'écrire un test unitaire sur un UITableViewController. La table présente une liste de "Decks". Le TableViewController est contenu dans un navigation controller, et possède à  gauche un bouton Plus. Le toucher doit ajouter un Deck. Voici le test:


<br />
- (void) testAddsACardOnLeftButtonAction<br />
{<br />
	UIBarButtonItem *leftButton = controller.navigationItem.leftBarButtonItem;<br />
	[controller performSelector:leftButton.action withObject:leftButton];<br />
	STAssertEquals(decks.count, (NSUInteger)1, @&quot;Should have an extra deck.&quot;);<br />
}<br />




Au départ, decks.count vaut 0.

Je récupère l'action associée au bouton de gauche, et la déclenche.

decks.count doit alors valoir 1.



Malheureusement, j'obtiens le warning "performSelector may cause a leak because its selector is unknown".



Pour l'instant, j'ai désactivé le warning en utilisant un #pragma. D'autres suggestions ?
Mots clés:

Réponses

  • AliGatorAliGator Membre, Modérateur
    Oui c'est un warning que j'ai déjà  eu et ça se comprend.

    En fait c'est parce que comme tu demandes d'exécuter un @selector dont il ne peut pas deviner le nom à  la compilation, il ne sait pas quelles conventions de nommage et donc de gestion mémoire ce selector respecte.



    En fait le warning est là  car si le @selector retourné par leftButton.action était un @selector(newObject:) ou @selector(objectCopy:), ton code générerait un leak, et le compilateur n'a aucun moyen de savoir si c'est le cas ou pas, donc il t'alerte que le risque est là  mais qu'il ne peut pas en être sûr (d'où le "MAY").



    Bien entendu, toi tu sais que, étant donné que ton @selector dans ce cas c'est une action d'un bouton, c'est forcément une méthode qui suit une convention autorelease. Et donc que tu n'auras pas de leak. Mais lui ne peut pas le deviner.



    En effet la solution la plus propre je pense c'est désactiver le warning autour de ce code, à  coup de "#pragma clang diagnostic push/ignored/pop".
  • CéroceCéroce Membre, Modérateur
    novembre 2012 modifié #3
    OK, je comprends tes explications, alors que je n'avais pas saisi celles données sur la page en lien.

    Bon, je fais juste taire le warning, alors image/thumbsup.gif' class='bbc_emoticon' alt='' />

    (puisqu'aucun objet n'est renvoyé, pas de risque de fuite).
  • AliGatorAliGator Membre, Modérateur
    novembre 2012 modifié #4
    En fait l'autre solution que tu peux faire c'est récupérer la fonction C associée au selector et la caster, comme ça tu ne devrais pas avoir le warning. Ne pas oublier quand tu cast le pointeur de fonction retourné par methodForSelector, d'y inclure les 2 arguments cachés "self" et "_cmd".


    // Les 2 premiers paramètres (cachés en Objective-C) sont le &quot;self&quot; de la méthode (son objet cible) et _cmd (le selector correspondant) ; le 3e est le paramètre sender de ton action<br />
    void (*actionFct)(id, SEL, id) = ( void(*)(id, SEL, id) )[leftButton.target methodForSelector:leftButton.action];<br />
    actionFct(leftButton.target, leftButton.action, leftButton);
    
    Bien sûr ça ne marchera que si l'action de ton leftButton prend bien le paramètre sender. Mais tu sais qu'on a plusieurs moyens d'écrire une IBAction pour un bouton, on peut ne pas lui passer de paramètre, ou lui passer le paramètre sender, ou lui passer le paramètre sender et event, c'est au choix... Donc idéalement si tu veux être robuste faudrait penser à  tester tous les cas (et ce que tu fasses performSelector et désactive le warning ou que tu fasses methodForSelector, d'ailleurs)
  • CéroceCéroce Membre, Modérateur
    ça me paraà®t moins lisible. À mon sens, dans la version actuelle on voit plus clairement qu'on lance l'appel de méthode, malgré les #pragma.
Connectez-vous ou Inscrivez-vous pour répondre.