J'ai entendu des gens dire que la méthode swizzling est une pratique dangereuse. Même le nom bouillonnant suggère que c'est un peu une triche.
La méthode Swizzling modifie le mappage de sorte que l'appel du sélecteur A invoque réellement l'implémentation B. Une de ses utilisations est d'étendre le comportement des classes source fermées.
Pouvons-nous formaliser les risques afin que quiconque décide d'utiliser le swizzling puisse prendre une décision en connaissance de cause si cela en vaut la peine pour ce qu'il essaie de faire.
Par exemple
- Nommer les collisions : si la classe étend ultérieurement ses fonctionnalités pour inclure le nom de méthode que vous avez ajouté, cela entraînera une énorme quantité de problèmes. Réduisez le risque en nommant judicieusement les méthodes accélérées.
ios
objective-c
swizzling
Robert
la source
la source
Réponses:
Je pense que c'est une très bonne question, et c'est dommage qu'au lieu de s'attaquer à la vraie question, la plupart des réponses aient contourné le problème et aient simplement dit de ne pas utiliser le swizzling.
Utiliser la méthode grésillement, c'est comme utiliser des couteaux tranchants dans la cuisine. Certaines personnes ont peur des couteaux tranchants parce qu'elles pensent qu'elles se coupent mal, mais la vérité est que les couteaux tranchants sont plus sûrs .
La méthode swizzling peut être utilisée pour écrire un code meilleur, plus efficace et plus facile à gérer. Il peut également être abusé et conduire à des bugs horribles.
Contexte
Comme pour tous les modèles de conception, si nous sommes pleinement conscients des conséquences du modèle, nous sommes en mesure de prendre des décisions plus éclairées sur son utilisation ou non. Les singletons sont un bon exemple de quelque chose qui est assez controversé, et pour une bonne raison - ils sont vraiment difficiles à mettre en œuvre correctement. Cependant, beaucoup de gens choisissent encore d'utiliser des singletons. La même chose peut être dite à propos du swizzling. Vous devez vous faire votre propre opinion une fois que vous comprenez parfaitement le bien et le mal.
Discussion
Voici quelques-uns des pièges de la méthode swizzling:
Ces points sont tous valables, et en les abordant, nous pouvons améliorer à la fois notre compréhension de la méthode swizzling ainsi que la méthodologie utilisée pour atteindre le résultat. Je prendrai chacun à la fois.
La méthode swizzling n'est pas atomique
Je n'ai pas encore vu une implémentation de la méthode swizzling qui est sûre à utiliser simultanément 1 . Ce n'est en fait pas un problème dans 95% des cas où vous souhaitez utiliser la méthode swizzling. Généralement, vous souhaitez simplement remplacer l'implémentation d'une méthode et vous souhaitez que cette implémentation soit utilisée pendant toute la durée de vie de votre programme. Cela signifie que vous devez faire votre méthode rapidement
+(void)load
. Laload
méthode de classe est exécutée en série au début de votre application. Vous n'aurez aucun problème avec la concurrence si vous effectuez votre navigation ici. Si vous deviez vous précipiter+(void)initialize
, cependant, vous pourriez vous retrouver avec une condition de concurrence dans votre implémentation de swizzling et l'exécution pourrait se retrouver dans un état étrange.Modifie le comportement du code non possédé
C'est un problème avec swizzling, mais c'est un peu le point. Le but est de pouvoir changer ce code. La raison pour laquelle les gens soulignent que c'est un gros problème est que vous ne changez pas seulement les choses pour l'instance pour
NSButton
laquelle vous voulez changer les choses, mais pour toutes lesNSButton
instances de votre application. Pour cette raison, vous devez être prudent lorsque vous tournez, mais vous n'avez pas besoin de l'éviter complètement.Pensez-y de cette façon ... si vous remplacez une méthode dans une classe et que vous n'appelez pas la méthode de super classe, vous pouvez provoquer des problèmes. Dans la plupart des cas, la super classe s'attend à ce que cette méthode soit appelée (sauf indication contraire). Si vous appliquez cette même pensée au swizzling, vous avez couvert la plupart des problèmes. Appelez toujours l'implémentation d'origine. Sinon, vous changez probablement trop pour être en sécurité.
Conflits de nommage possibles
Les conflits de dénomination sont un problème dans Cocoa. Nous préfixons fréquemment les noms de classe et les noms de méthode dans les catégories. Malheureusement, les conflits de noms sont un fléau dans notre langue. Dans le cas du swizzling, cependant, ils ne doivent pas l'être. Nous avons juste besoin de changer légèrement notre façon de penser à la méthode. Le swizzling se fait comme ceci:
Cela fonctionne très bien, mais que se passerait-il s'il
my_setFrame:
était défini ailleurs? Ce problème n'est pas propre au swizzling, mais nous pouvons le contourner quand même. La solution de contournement a l'avantage supplémentaire de résoudre également les autres pièges. Voici ce que nous faisons à la place:Bien que cela ressemble un peu moins à Objective-C (car il utilise des pointeurs de fonction), cela évite tout conflit de dénomination. En principe, il fait exactement la même chose que le swizzling standard. Cela peut être un peu un changement pour les personnes qui utilisent le swizzling tel qu'il a été défini depuis un certain temps, mais au final, je pense que c'est mieux. La méthode de swizzling est définie ainsi:
Swizzling en renommant les méthodes modifie les arguments de la méthode
Ceci est le grand dans mon esprit. C'est la raison pour laquelle le swizzling de la méthode standard ne doit pas être effectué. Vous modifiez les arguments passés à l'implémentation de la méthode d'origine. C'est là que ça se passe:
Ce que fait cette ligne, c'est:
Qui utilisera le runtime pour rechercher l'implémentation de
my_setFrame:
. Une fois l'implémentation trouvée, il invoque l'implémentation avec les mêmes arguments que ceux donnés. L'implémentation qu'il trouve est l'implémentation d'originesetFrame:
, donc il continue et appelle cela, mais l'_cmd
argument n'est passetFrame:
comme il devrait être. C'est maintenantmy_setFrame:
. L'implémentation d'origine est appelée avec un argument qu'elle ne s'attendait pas à recevoir. Ce n'est pas bien.Il existe une solution simple - utilisez la technique alternative de swizzling définie ci-dessus. Les arguments resteront inchangés!
L'ordre des swizzles est important
L'ordre dans lequel les méthodes se propagent compte. En supposant
setFrame:
que n'est défini que surNSView
, imaginez cet ordre de choses:Que se passe-t-il lorsque la méthode
NSButton
est accélérée? Eh bien, la plupart des accélérations garantiront qu'elles ne remplacent pas l'implémentation desetFrame:
pour toutes les vues, donc elles afficheront la méthode d'instance. Cela utilisera l'implémentation existante pour redéfinirsetFrame:
dans laNSButton
classe afin que l'échange d'implémentations n'affecte pas toutes les vues. L'implémentation existante est celle définie surNSView
. La même chose se produira lors de l'activationNSControl
(en utilisant à nouveau l'NSView
implémentation).Lorsque vous appelez
setFrame:
un bouton, il appellera donc votre méthode swizzled, puis passera directement à lasetFrame:
méthode initialement définie surNSView
. Les implémentationsNSControl
etNSView
swizzled ne seront pas appelées.Et si la commande était:
Étant donné que le swizzling de la vue a lieu en premier, le swizzling de contrôle pourra tirer la bonne méthode. De même, puisque le swizzling du contrôle était avant le swizzling du bouton, le bouton affichera la mise en œuvre du swizzled du contrôle
setFrame:
. C'est un peu déroutant, mais c'est le bon ordre. Comment pouvons-nous assurer cet ordre de choses?Encore une fois, utilisez simplement
load
pour accélérer les choses. Si vous vous précipitezload
et que vous apportez uniquement des modifications à la classe en cours de chargement, vous serez en sécurité. Laload
méthode garantit que la méthode de chargement de super classe sera appelée avant toutes les sous-classes. Nous obtiendrons la bonne commande exacte!Difficile à comprendre (semble récursif)
En regardant une méthode swizzled traditionnellement définie, je pense qu'il est vraiment difficile de dire ce qui se passe. Mais en regardant la façon alternative dont nous avons fait le tour ci-dessus, c'est assez facile à comprendre. Celui-ci a déjà été résolu!
Difficile à déboguer
L'une des confusions pendant le débogage est de voir une étrange trame où les noms brouillés sont mélangés et tout se confond dans votre tête. Encore une fois, l'implémentation alternative résout ce problème. Vous verrez des fonctions clairement nommées dans les backtraces. Pourtant, le swizzling peut être difficile à déboguer car il est difficile de se rappeler quel impact le swizzling a. Documentez bien votre code (même si vous pensez que vous êtes le seul à le voir). Suivez les bonnes pratiques et tout ira bien. Il n'est pas plus difficile à déboguer que le code multi-thread.
Conclusion
La méthode swizzling est sûre si elle est utilisée correctement. Une simple mesure de sécurité que vous pouvez prendre consiste à ne faire que vous précipiter
load
. Comme beaucoup de choses en programmation, cela peut être dangereux, mais comprendre les conséquences vous permettra de l'utiliser correctement.1 En utilisant la méthode de swizzling définie ci-dessus, vous pourriez rendre les choses sûres pour les fils si vous utilisiez des trampolines. Vous auriez besoin de deux trampolines. Au début de la méthode, vous devez affecter le pointeur de fonction
store
, à une fonction qui a tourné jusqu'à ce que l'adresse vers laquellestore
pointé change. Cela éviterait toute condition de concurrence critique dans laquelle la méthode swizzled a été appelée avant que vous ne puissiez définir lestore
pointeur de fonction. Vous devrez alors utiliser un trampoline dans le cas où l'implémentation n'est pas déjà définie dans la classe et avoir la recherche de trampoline et appeler correctement la méthode de super classe. La définition de la méthode afin qu'elle recherche dynamiquement la super implémentation garantira que l'ordre des appels accélérés n'a pas d'importance.la source
Je vais d'abord définir exactement ce que je veux dire par méthode swizzling:
La méthode swizzling est plus générale que cela, mais c'est le cas qui m'intéresse.
Dangers:
Changements dans la classe d'origine . Nous ne possédons pas la classe que nous parcourons. Si la classe change, notre swizzle peut cesser de fonctionner.
Difficile à entretenir . Non seulement vous devez écrire et maintenir la méthode accélérée. vous devez écrire et maintenir le code qui préforme le swizzle
Difficile à déboguer . Il est difficile de suivre le flux d'un swizzle, certaines personnes peuvent même ne pas se rendre compte que le swizzle a été préformé. S'il y a des bogues introduits par le swizzle (peut-être dû à des changements dans la classe d'origine), ils seront difficiles à résoudre.
En résumé, vous devez réduire au minimum le swizzling et considérer comment les changements dans la classe d'origine peuvent affecter votre swizzle. Vous devez également clairement commenter et documenter ce que vous faites (ou simplement l'éviter complètement).
la source
Ce n'est pas le tourbillon lui-même qui est vraiment dangereux. Le problème est, comme vous le dites, qu'il est souvent utilisé pour modifier le comportement des classes de framework. C'est en supposant que vous savez quelque chose sur le fonctionnement de ces cours privés qui est "dangereux". Même si vos modifications fonctionnent aujourd'hui, il y a toujours une chance qu'Apple change la classe à l'avenir et provoque la rupture de votre modification. De plus, si de nombreuses applications différentes le font, il est d'autant plus difficile pour Apple de changer le cadre sans casser beaucoup de logiciels existants.
la source
Utilisé avec soin et sagesse, il peut conduire à un code élégant, mais généralement, il conduit à un code confus.
Je dis qu'il devrait être interdit, sauf si vous savez qu'il présente une opportunité très élégante pour une tâche de conception particulière, mais vous devez savoir clairement pourquoi il s'applique bien à la situation et pourquoi les alternatives ne fonctionnent pas élégamment pour la situation .
Par exemple, une bonne application de la méthode swizzling est is swizzling, c'est ainsi qu'ObjC met en œuvre l'observation des valeurs clés.
Un mauvais exemple pourrait être de s'appuyer sur la méthode swizzling pour étendre vos classes, ce qui conduit à un couplage extrêmement élevé.
la source
Bien que j'aie utilisé cette technique, je voudrais souligner que:
la source
Je pense que le plus grand danger est de créer de nombreux effets secondaires indésirables, complètement par accident. Ces effets secondaires peuvent se présenter comme des «bogues» qui à leur tour vous conduisent sur la mauvaise voie pour trouver la solution. D'après mon expérience, le danger est un code illisible, déroutant et frustrant. Un peu comme quand quelqu'un sur-utilise les pointeurs de fonction en C ++.
la source
Vous pouvez vous retrouver avec un code étrange comme
à partir du code de production réel lié à une certaine magie de l'interface utilisateur.
la source
Le swizzling de méthode peut être très utile dans les tests unitaires.
Il vous permet d'écrire un objet simulé et d'utiliser cet objet simulé à la place de l'objet réel. Votre code doit rester propre et votre test unitaire a un comportement prévisible. Supposons que vous souhaitiez tester du code utilisant CLLocationManager. Votre test unitaire pourrait accélérer startUpdatingLocation afin qu'il fournisse un ensemble prédéterminé d'emplacements à votre délégué et que votre code n'ait pas à changer.
la source