Je reçois l'avertissement suivant du compilateur ARC:
"performSelector may cause a leak because its selector is unknown".
Voici ce que je fais:
[_controller performSelector:NSSelectorFromString(@"someMethod")];
Pourquoi ai-je 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 changer mon code pour ne plus recevoir cet avertissement?
ios
objective-c
memory-leaks
automatic-ref-counting
Eduardo Scoz
la source
la source
Réponses:
Solution
Le compilateur l'avertit pour une raison. Il est très rare que cet avertissement soit simplement ignoré et il est facile de contourner ce problème. Voici comment:
Ou plus laconiquement (bien que difficile à lire et sans le garde):
Explication
Ce qui se passe ici, c'est que vous demandez au contrôleur le pointeur de la fonction C pour la méthode correspondant au contrôleur. Tous
NSObject
répondent àmethodForSelector:
, mais vous pouvez également utiliserclass_getMethodImplementation
dans le runtime Objective-C (utile si vous n'avez qu'une référence de protocole, commeid<SomeProto>
). Ces pointeurs de fonction sont appelésIMP
s et sont de simplestypedef
pointeurs de fonction ed (id (*IMP)(id, SEL, ...)
) 1 . Cela peut être proche de la signature de méthode 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 ARC a besoin (y compris les deux arguments cachés implicitesself
et_cmd
de chaque appel de méthode Objective-C). Ceci est géré dans la troisième ligne (le(void *)
côté droit indique simplement au compilateur que vous savez ce que vous faites et ne pas générer d'avertissement car les types de pointeurs ne correspondent pas).Enfin, vous appelez le pointeur de fonction 2 .
Exemple complexe
Lorsque le sélecteur prend des arguments ou renvoie une valeur, vous devrez changer un peu les choses:
Raison 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 pourrait être quelque chose:
void
,int
,char
,NSString *
,id
, etc. ARC reçoit normalement ces informations de l' en- tête du type d'objet que vous travaillez avec. 3ARC ne prend en compte que 4 éléments pour la valeur de retour: 4
void
,int
, etc.)init
/copy
ou attribuées avecns_returns_retained
)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. Ainsi, vous pourriez finir par créer une fuite si votre objet est censé être libéré comme au n ° 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 ce retour
void
ou d'autres non-objets, vous pouvez activer les fonctionnalités du compilateur pour ignorer l'avertissement, mais cela peut être dangereux. J'ai vu Clang parcourir quelques itérations sur la façon dont il gère les valeurs de retour qui ne sont pas affectées aux variables locales. Il n'y a aucune raison qu'avec ARC activé, il ne puisse pas conserver et libérer la valeur d'objet retournéemethodForSelector:
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 objet non (y comprisvoid
), vous pourriez vous retrouver avec une valeur de pointeur de mémoire conservée / libérée et planter.Arguments supplémentaires
Une considération est que c'est le même avertissement qui se produira
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 les paramètres consommés , et si la méthode consomme le paramètre, vous finirez probablement par envoyer un message à un zombie et à planter. Il existe des moyens de contourner ce problème avec le casting ponté, mais il serait vraiment préférable d'utiliser simplement laIMP
méthodologie de pointeur de fonction ci-dessus. Étant donné que les paramètres consommés sont rarement un problème, cela ne devrait pas se produire.Sélecteurs statiques
Fait intéressant, le compilateur ne se plaindra pas des sélecteurs déclarés statiquement:
La raison en est que le compilateur est réellement capable 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 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 de code, je suis vide. Quelqu'un s'il vous plaît partager s'ils ont eu une expérience où le silence de cet avertissement était nécessaire (et ce qui précède ne gère pas les choses correctement).
Plus
Il est possible de créer un
NSMethodInvocation
pour gérer cela également, mais cela nécessite beaucoup plus de frappe et est également plus lent, il n'y a donc aucune raison de le faire.Histoire
Lorsque la
performSelector:
famille de méthodes a été ajoutée pour la première fois à Objective-C, ARC n'existait pas. Lors de la création d'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 devait être gérée lors de l'envoi de messages arbitraires via un sélecteur nommé. Dans Objective-C, les développeurs peuvent le faire en utilisant des transtypages de style C sur des pointeurs de fonction bruts.Avec l'introduction de Swift, Apple a documenté la
performSelector:
famille de méthodes comme "intrinsèquement dangereuses" et elles ne sont pas disponibles pour Swift.Au fil du temps, nous avons vu cette progression:
performSelector:
(gestion manuelle de la mémoire)performSelector:
performSelector:
ces méthodes et ne les documente pas 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 dans Objective-C ainsi que dans de nombreux autres langages de programmation.
1 Toutes les méthodes Objective-C ont deux arguments cachés,
self
et_cmd
qui sont implicitement ajoutés lorsque vous appelez une méthode.2 L' appel d'une
NULL
fonction n'est pas sûr en C. Le gardien utilisé pour vérifier la présence du contrôleur s'assure que nous avons un objet. Nous savons donc que nous aurons unIMP
demethodForSelector:
(bien qu'il puisse être_objc_msgForward
, l' entrée dans le système de transfert des messages). Fondamentalement, avec la garde en place, nous savons que nous avons une fonction à appeler.3 En fait, il est possible qu'il obtienne des informations erronées si vous déclarez des objets comme
id
et que vous n'importez pas tous les en-têtes. Vous pourriez vous retrouver avec des plantages dans le code que le compilateur pense être correct. C'est très rare, mais cela pourrait arriver. Habituellement, 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 les valeurs de retour conservées et les valeurs de retour non retenues pour plus de détails.
la source
performSelector:
méthodes ne sont pas implémentées de cette façon. Ils ont une signature de méthode stricte (renvoyantid
, prenant un ou deuxid
s), donc aucun type primitif n'a besoin d'être manipulé.Cannot initialize a variable of type 'CGRect (*)(__strong id, SEL, CGRect, UIView *__strong)' with an rvalue of type 'void *'
lors de l'utilisation du dernier Xcode. (5.1.1) Pourtant, j'ai beaucoup appris!void (*func)(id, SEL) = (void *)imp;
ne compile pas, je l'ai remplacé parvoid (*func)(id, SEL) = (void (*)(id, SEL))imp;
void (*func)(id, SEL) = (void *)imp;
pour<…> = (void (*))imp;
ou<…> = (void (*) (id, SEL))imp;
Dans le compilateur LLVM 3.0 dans Xcode 4.2, vous pouvez supprimer l'avertissement comme suit:
Si vous obtenez l'erreur à plusieurs endroits et que vous souhaitez utiliser le système de macros C pour masquer les pragmas, vous pouvez définir une macro pour faciliter la suppression de l'avertissement:
Vous pouvez utiliser la macro comme ceci:
Si vous avez besoin du résultat du message effectué, vous pouvez le faire:
la source
pop
etpush
est beaucoup plus propre et plus sûr.if ([_target respondsToSelector:_selector]) {
logique ou une logique similaire.Ma conjecture à ce sujet est la suivante: puisque le sélecteur est inconnu du compilateur, ARC ne peut pas appliquer une gestion de la mémoire appropriée.
En fait, il y a des moments où la gestion de la mémoire est liée au nom de la méthode par une convention spécifique. Plus précisément, je pense aux constructeurs de commodité et aux méthodes de fabrication ; l'ancien restitue par convention un objet libéré automatiquement; ce dernier un objet retenu. 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 mémoire appropriée.
Si c'est correct, je pense que vous pouvez utiliser votre code en toute sécurité, à condition de vous assurer que tout va bien pour la gestion de la mémoire (par exemple, que vos méthodes ne retournent pas les objets qu'elles allouent).
la source
__attribute
à chaque méthode expliquant sa gestion de la mémoire. Mais cela empêche également le complice de gérer correctement ce modèle (un modèle qui était très courant auparavant, mais qui a été remplacé par des modèles plus robustes ces dernières années).SEL
et assigner différents sélecteurs selon la situation? Bravo, langage dynamique ...Dans les paramètres de construction de votre projet , sous Autres indicateurs d'avertissement (
WARNING_CFLAGS
), ajoutez-Wno-arc-performSelector-leaks
Assurez-vous maintenant que le sélecteur que vous appelez n'entraîne pas la conservation ou la copie de votre objet.
la source
Comme solution de contournement jusqu'à ce que le compilateur permette de remplacer l'avertissement, vous pouvez utiliser le runtime
au lieu de
Tu devras
la source
[_controller performSelector:NSSelectorFromString(@"someMethod")];
et ceobjc_msgSend(_controller, NSSelectorFromString(@"someMethod"));
n'est pas équivalent! Jetez un œil aux asymétries de signature de méthode et à une grande faiblesse du typage faible d'Objective-C, ils expliquent le problème en profondeur.Pour ignorer l'erreur uniquement dans le fichier avec le sélecteur perform, ajoutez un #pragma comme suit:
Cela ignorerait l'avertissement sur cette ligne, mais l'autoriserait tout au long du reste de votre projet.
la source
#pragma clang diagnostic warning "-Warc-performSelector-leaks"
. Je sais que si je désactive un avertissement, j'aime le réactiver le plus tôt possible, donc je ne laisse pas accidentellement passer un autre avertissement imprévu. Il est peu probable que ce soit un problème, mais c'est juste ma pratique chaque fois que je désactive un avertissement.#pragma clang diagnostic warning push
avant d'effectuer des modifications et#pragma clang diagnostic warning pop
pour restaurer l'état précédent. Utile si vous désactivez des charges et que vous ne voulez pas avoir beaucoup de lignes de pragma réactivées dans votre code.Étrange mais vrai: si cela est acceptable (c'est-à-dire que le résultat est nul et que cela ne vous dérange pas de laisser le cycle runloop une fois), ajoutez un délai, même s'il est nul:
Cela supprime l'avertissement, probablement parce qu'il rassure le compilateur qu'aucun objet ne peut être retourné et mal géré.
la source
Voici une macro mise à jour basée sur la réponse donnée ci-dessus. Celui-ci devrait vous permettre d'encapsuler votre code même avec une déclaration de retour.
la source
return
n'a pas besoin d'être à l'intérieur de la macro;return SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING([_target performSelector:_action withObject:self]);
fonctionne également et semble plus sain.Ce code n'implique pas de drapeaux de compilateur ni d'appels d'exécution directs:
NSInvocation
permet de définir plusieurs arguments, contrairement àperformSelector
ce qui fonctionnera sur n'importe quelle méthode.la source
Eh bien, beaucoup de réponses ici, mais comme c'est un peu différent, en combinant quelques réponses, je pensais que je les mettrais. J'utilise une catégorie NSObject qui vérifie que le sélecteur retourne vide, et supprime également le compilateur Attention.
la source
Pour la postérité, j'ai décidé de jeter mon chapeau dans le ring :)
Récemment , j'ai été voir de plus en plus loin de la restructuration du
target
/selector
paradigme, en faveur des choses telles que des protocoles, des blocs, etc. Cependant, il y a une solution de remplacement pourperformSelector
que je l' ai utilisé quelques fois:Ceux-ci semblent être un remplacement propre, sans danger pour l'ARC et presque identique
performSelector
sans avoir trop à faire avecobjc_msgSend()
.Cependant, je n'ai aucune idée s'il existe un analogue disponible sur iOS.
la source
[[UIApplication sharedApplication] sendAction: to: from: forEvent:]
. Je l'ai étudié une fois, mais c'est un peu gênant d'utiliser une classe liée à l'interface utilisateur au milieu de votre domaine ou service juste pour faire un appel dynamique .. Merci d'avoir inclus cela cependant!id
from-performSelector:...
to:
est nul, ce qui n'est pas le cas. Il va directement à l'objet ciblé sans vérification préalable. Il n'y a donc pas "plus de frais généraux". Ce n'est pas une excellente solution, mais la raison que vous donnez n'est pas la raison. :)La réponse de Matt Galloway sur ce sujet explique pourquoi:
Il semble qu'il soit généralement sûr de supprimer l'avertissement si vous ignorez la valeur de retour. Je ne sais pas quelle est la meilleure pratique si vous avez vraiment besoin d'obtenir un objet conservé de performSelector - autre que "ne faites pas ça".
la source
@ c-road fournit le bon lien avec la description du problème ici . Ci-dessous, vous pouvez voir mon exemple, lorsque performSelector provoque une fuite de mémoire.
La seule méthode qui provoque une fuite de mémoire dans mon exemple est CopyDummyWithLeak. La raison en est que ARC ne sait pas, que copySelector renvoie un objet conservé.
Si vous exécutez l'outil Memory Leak Tool, vous pouvez voir l'image suivante: ... et il n'y a aucune fuite de mémoire dans les autres cas:
la source
Pour rendre la macro de Scott Thompson plus générique:
Ensuite, utilisez-le comme ceci:
la source
Ne supprimez pas les avertissements!
Il n'y a pas moins de 12 solutions alternatives au bricolage avec le compilateur.
Alors que vous êtes intelligent au moment de la première mise en œuvre, peu d'ingénieurs sur Terre peuvent suivre vos traces, et ce code finira par se casser.
Itinéraires sûrs:
Toutes ces solutions fonctionneront, avec un certain degré de variation par rapport à votre intention initiale. Supposons que cela
param
puisse êtrenil
si vous le souhaitez:Route sûre, même comportement conceptuel:
Itinéraire sûr, comportement légèrement différent:
(Voir cette réponse)
Utilisez n'importe quel fil au lieu de
[NSThread mainThread]
.Routes dangereuses
Nécessite une sorte de mise en sourdine du compilateur, qui est susceptible de se casser. Notez qu'à l'heure actuelle, il s'est cassé dans Swift .
la source
performSelectorOnMainThread
n'est pas un bon moyen de faire taire l'avertissement et cela a des effets secondaires. (cela ne résout pas la fuite de mémoire) Le supplément#clang diagnostic ignored
supprime explicitement l'avertissement de manière très claire.- (void)
méthode non est le vrai problème.Étant donné que vous utilisez ARC, vous devez utiliser iOS 4.0 ou une version ultérieure. Cela signifie que vous pouvez utiliser des blocs. Si au lieu de vous souvenir du sélecteur pour vous exécuter, vous preniez un bloc, ARC serait en mesure de mieux suivre ce qui se passe réellement et vous n'auriez pas à courir le risque d'introduire accidentellement une fuite de mémoire.
la source
self
via un ivar (par exempleivar
au lieu deself->ivar
).Au lieu d'utiliser l'approche par blocs, ce qui m'a posé quelques problèmes:
J'utiliserai NSInvocation, comme ceci:
la source
Si vous n'avez pas besoin de passer d'arguments, une solution de contournement simple est à utiliser
valueForKeyPath
. C'est même possible sur unClass
objet.la source
Vous pouvez également utiliser un protocole ici. Alors, créez un protocole comme ceci:
Dans votre classe qui doit appeler votre sélecteur, vous avez alors un @property.
Lorsque vous devez appeler
@selector(doSomethingWithObject:)
dans une instance de MyObject, procédez comme suit:la source