1299 votes

performSelector peut provoquer une fuite car son sélecteur est inconnu.

Je reçois l'avertissement suivant de la part du compilateur ARC :

"performSelector may cause a leak because its selector is unknown".

Voilà ce que je fais :

[_controller performSelector:NSSelectorFromString(@"someMethod")];

Pourquoi est-ce que je reçois cet avertissement ? Je comprends que le compilateur ne peut pas vérifier si le sélecteur existe ou non, mais pourquoi cela provoquerait-il une fuite ? Et comment puis-je modifier mon code pour ne plus recevoir cet avertissement ?

1243voto

wbyoung Points 9428

Solution

Le compilateur vous avertit à ce sujet pour une bonne raison. Il est très rare que cet avertissement doive être simplement ignoré, et il est facile de le contourner. Voici comment :

if (!_controller) { return; }
SEL selector = NSSelectorFromString(@"someMethod");
IMP imp = [_controller methodForSelector:selector];
void (*func)(id, SEL) = (void *)imp;
func(_controller, selector);

Ou plus succinctement (bien que difficile à lire & sans le gardien) :

SEL selector = NSSelectorFromString(@"someMethod");
((void (*)(id, SEL))[_controller methodForSelector:selector])(_controller, selector);

Explication

Ce qui se passe ici, c'est que vous demandez au contrôleur le pointeur de fonction C pour la méthode correspondant au contrôleur. Tous les sites NSObject répondent à methodForSelector: mais vous pouvez aussi utiliser class_getMethodImplementation dans le runtime Objective-C (utile si vous n'avez qu'une référence de protocole, comme id<SomeProto> ). Ces pointeurs de fonction sont appelés IMP et sont simples typedef ed pointeurs de fonction ( id (*IMP)(id, SEL, ...) ) 1 . Cela peut être proche de la signature réelle de la méthode, mais ne correspondra pas toujours exactement.

Une fois que vous avez le IMP vous devez le convertir en un pointeur de fonction qui inclut tous les détails dont l'ARC a besoin (y compris les deux arguments implicites cachés). self y _cmd de chaque appel de méthode Objective-C). Ceci est traité dans la troisième ligne (la (void *) sur le côté droit indique simplement au compilateur que vous savez ce que vous faites et qu'il ne doit pas générer d'avertissement puisque les types de pointeurs ne correspondent pas).

Enfin, vous appelez la fonction pointeur 2 .

Exemple complexe

Lorsque le sélecteur prend des arguments ou renvoie une valeur, vous devrez modifier quelque peu les choses :

SEL selector = NSSelectorFromString(@"processRegion:ofView:");
IMP imp = [_controller methodForSelector:selector];
CGRect (*func)(id, SEL, CGRect, UIView *) = (void *)imp;
CGRect result = _controller ?
  func(_controller, selector, someRect, someView) : CGRectZero;

Motivation de l'avertissement

La raison de cet avertissement est qu'avec ARC, le runtime doit savoir quoi faire avec le résultat de la méthode que vous appelez. Le résultat peut être n'importe quoi : void , int , char , NSString * , id etc. ARC obtient normalement ces informations à partir de l'en-tête du type d'objet avec lequel vous travaillez. 3

Il n'y a vraiment que 4 choses que l'ARC prendrait en compte pour la valeur de retour : 4

  1. Ignorer les types de non-objets ( void , int etc)
  2. Conserver la valeur de l'objet, puis le libérer lorsqu'il n'est plus utilisé (hypothèse standard).
  3. Libérer les valeurs des nouveaux objets lorsqu'ils ne sont plus utilisés (les méthodes de l'option init / copy famille ou attribué avec ns_returns_retained )
  4. Ne rien faire et supposer que la valeur de l'objet retourné sera valide dans la portée locale (jusqu'à ce que le pool de libération le plus interne soit vidé, attribué avec ns_returns_autoreleased )

L'appel à methodForSelector: suppose que la valeur de retour de la méthode qu'il appelle est un objet, mais ne le conserve pas/ne le libère pas. Vous pouvez donc créer une fuite si votre objet est censé être libéré comme dans le point 3 ci-dessus (c'est-à-dire que la méthode que vous appelez renvoie un nouvel objet).

Pour les sélecteurs que vous essayez d'appeler qui retournent void ou d'autres non-objets, vous pourriez activer les fonctionnalités du compilateur pour ignorer l'avertissement, mais cela peut être dangereux. J'ai vu Clang passer par plusieurs itérations sur la façon dont il gère les valeurs de retour qui ne sont pas assignées à des variables locales. Il n'y a aucune raison pour qu'avec l'ARC activé, il ne puisse pas retenir et libérer la valeur de l'objet retourné par methodForSelector: même si vous ne voulez pas l'utiliser. Du point de vue du compilateur, c'est un objet après tout. Cela signifie que si la méthode que vous appelez, someMethod retourne un non-objet (y compris void ), vous pourriez vous retrouver avec une valeur de pointeur poubelle retenue/libérée et vous planter.

Arguments supplémentaires

Une considération est que c'est le même avertissement qui se produira avec performSelector:withObject: et vous pourriez rencontrer des problèmes similaires en ne déclarant pas comment cette méthode consomme les paramètres. ARC permet de déclarer paramètres consommés et si la méthode consomme le paramètre, vous finirez probablement par envoyer un message à un zombie et par vous planter. Il existe des moyens de contourner ce problème avec le bridged casting, mais il serait vraiment préférable d'utiliser simplement la méthode IMP et la méthodologie du pointeur de fonction ci-dessus. Comme les paramètres consommés sont rarement un problème, il est peu probable que cela se produise.

Sélecteurs statiques

Il est intéressant de noter que le compilateur ne se plaint pas des sélecteurs déclarés de manière statique :

[_controller performSelector:@selector(someMethod)];

La raison en est que le compilateur est en mesure d'enregistrer toutes les informations sur le sélecteur et l'objet pendant la compilation. Il n'a pas besoin de faire d'hypothèses sur quoi que ce soit. (J'ai vérifié cela il y a un an environ en regardant la source, mais je n'ai pas de référence pour le moment).

Suppression

En essayant de penser à une situation où la suppression de cet avertissement serait nécessaire et une bonne conception du code, je n'arrive à rien. Quelqu'un pourrait-il me dire s'il a eu une expérience où la suppression de cet avertissement était nécessaire (et où le code ci-dessus ne gère pas les choses correctement) ?

Plus de

Il est possible de constituer un NSMethodInvocation pour gérer cela également, mais cela nécessite beaucoup plus de saisie et est également plus lent, il y a donc peu de raisons de le faire.

Histoire

Lorsque le performSelector: a été ajoutée à Objective-C, ARC n'existait pas. Lors de la création de l'ARC, Apple a décidé qu'un avertissement devait être généré pour ces méthodes afin de guider les développeurs vers l'utilisation d'autres moyens pour définir explicitement comment la mémoire doit être gérée lors de l'envoi de messages arbitraires via un sélecteur nommé. En Objective-C, les développeurs peuvent le faire en utilisant des casts de style C sur les pointeurs de fonction bruts.

Avec l'introduction de Swift, Apple a documenté el performSelector: La famille de méthodes Swift est considérée comme "intrinsèquement dangereuse" et n'est pas disponible pour Swift.

Au fil du temps, nous avons constaté cette progression :

  1. Les premières versions d'Objective-C permettent performSelector: (gestion manuelle de la mémoire)
  2. Objective-C avec ARC avertit de l'utilisation de performSelector:
  3. Swift n'a pas accès à performSelector: et documente ces méthodes comme "intrinsèquement dangereuses"

L'idée d'envoyer des messages sur la base d'un sélecteur nommé n'est cependant pas une fonctionnalité "intrinsèquement dangereuse". Cette idée est utilisée avec succès depuis longtemps en Objective-C ainsi que dans de nombreux autres langages de programmation.


1 Toutes les méthodes Objective-C ont deux arguments cachés, self y _cmd qui sont implicitement ajoutés lorsque vous appelez une méthode.

2 Appeler un NULL n'est pas sûre en C. La garde utilisée pour vérifier la présence du contrôleur assure que nous avons un objet. Nous savons donc que nous obtiendrons un IMP de methodForSelector: (bien que cela puisse être _objc_msgForward l'entrée dans le système de transmission des messages). En gros, avec la garde en place, nous savons que nous avons une fonction à appeler.

3 En fait, il est possible qu'il obtienne une information erronée si vous déclarez vos objets en tant que id et vous n'importez pas tous les en-têtes. Vous pourriez vous retrouver avec des plantages dans un code que le compilateur pense être bon. C'est très rare, mais cela peut arriver. En général, vous recevrez simplement un avertissement indiquant qu'il ne sait pas laquelle des deux signatures de méthode choisir.

4 Voir la référence ARC sur valeurs de retour retenues y valeurs de retour non conservées pour plus de détails.

1189voto

Scott Thompson Points 10516

Dans le compilateur LLVM 3.0 de Xcode 4.2, vous pouvez supprimer l'avertissement comme suit :

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    [self.ticketTarget performSelector: self.ticketAction withObject: self];
#pragma clang diagnostic pop

Si vous obtenez l'erreur à plusieurs endroits et que vous souhaitez utiliser le système de macro C pour masquer les pragmas, vous pouvez définir une macro pour faciliter la suppression de l'avertissement :

#define SuppressPerformSelectorLeakWarning(Stuff) \
    do { \
        _Pragma("clang diagnostic push") \
        _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
        Stuff; \
        _Pragma("clang diagnostic pop") \
    } while (0)

Vous pouvez utiliser la macro comme ceci :

SuppressPerformSelectorLeakWarning(
    [_target performSelector:_action withObject:self]
);

Si vous avez besoin du résultat du message exécuté, vous pouvez procéder ainsi :

id result;
SuppressPerformSelectorLeakWarning(
    result = [_target performSelector:_action withObject:self]
);

208voto

sergio Points 52422

Ma supposition à ce sujet est la suivante : puisque le sélecteur est inconnu du compilateur, ARC ne peut pas appliquer une gestion correcte de la mémoire.

En fait, il arrive que la gestion de la mémoire soit liée au nom de la méthode par une convention spécifique. Plus précisément, je pense à Constructeurs de commodité contre faire les premières retournent par convention un objet autoreleased, les secondes un objet retained. La convention est basée sur les noms du sélecteur, donc si le compilateur ne connaît pas le sélecteur, il ne peut pas appliquer la règle de gestion de la mémoire appropriée.

Si cela est correct, je pense que vous pouvez utiliser votre code en toute sécurité, à condition de vous assurer que tout est correct en ce qui concerne la gestion de la mémoire (par exemple, que vos méthodes ne renvoient pas les objets qu'elles allouent).

121voto

0xced Points 10972

Dans votre projet Paramètres de construction , sous Autres signaux d'alerte ( WARNING_CFLAGS ), ajoutez
-Wno-arc-performSelector-leaks

Maintenant, assurez-vous que le sélecteur que vous appelez ne provoque pas la rétention ou la copie de votre objet.

110voto

jluckyiv Points 2407

Comme solution de rechange jusqu'à ce que le compilateur permette de remplacer l'avertissement, vous pouvez utiliser le runtime.

Vous avez besoin d'un en-tête :

#import <objc/message.h>

Alors essayez ci-dessous :

// For strict compilers.
((id(*)(id,SEL))objc_msgSend)(_controller, sel_getUid("someMethod"));

OU

// Old answer's code:
objc_msgSend(_controller, NSSelectorFromString(@"someMethod"));

Au lieu de :

[_controller performSelector:NSSelectorFromString(@"someMethod")];

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X