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:
if (!_controller) { return; }
SEL selector = NSSelectorFromString(@"someMethod");
IMP imp = [_controller methodForSelector:selector];
void (*func)(id, SEL) = (void *)imp;
func(_controller, selector);
Ou plus laconiquement (bien que difficile à lire et sans le garde):
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 la fonction C pour la méthode correspondant au contrôleur. Tous NSObject
répondent à methodForSelector:
, mais vous pouvez également 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
s et sont de simples typedef
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 implicites self
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:
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;
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. 3
ARC ne prend en compte que 4 éléments pour la valeur de retour: 4
- Ignorer types non-objet (
void
, int
, etc.)
- Conserver la valeur de l'objet, puis la relâcher lorsqu'elle n'est plus utilisée (hypothèse standard)
- Libérer de nouvelles valeurs d'objet lorsqu'elles ne sont plus utilisées (méthodes dans la famille
init
/ copy
ou attribuées avec ns_returns_retained
)
- Ne rien faire et supposer que la valeur d'objet retournée sera valide dans la portée locale (jusqu'à ce que le pool de versions 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. 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ée 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 objet non (y compris void
), 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 la IMP
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:
[_controller performSelector:@selector(someMethod)];
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:
- Les premières versions d'Objective-C permettent
performSelector:
(gestion manuelle de la mémoire)
- Objective-C avec ARC met en garde contre l'utilisation de
performSelector:
- Swift n'a pas accès à
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 un IMP
de methodForSelector:
(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.