220 votes

Quels sont les Dangers de la Méthode Swizzling en Objective-C?

J'ai entendu des gens de l'état que la méthode swizzling est une pratique dangereuse. Même le nom swizzling sugests que c'est un peu de la triche.

Méthode Swizzling est la modification de la cartographie, de sorte que l'appel à Un sélecteur de va invoquer la mise en œuvre B. Une utilisation de ceci est d'étendre le comportement de code source fermé classes.

Peut-on formaliser les risques de sorte que n'importe qui qui est de décider d'utiliser swizzling pouvez prendre une décision éclairée si cela vaut la peine pour ce qu'ils essaient de faire.

E. g.

  • Les conflits de noms: Si la classe plus tard étend ses fonctionnalités à inclure le nom de la méthode que vous avez ajouté, il sera la cause d'une manière énorme de problèmes. Réduire le risque sensiblement de nommage swizzled méthodes.

409voto

wbyoung Points 9428

Je pense que c'est une très bonne question, et c'est une honte que, plutôt que de s'attaquer à la vraie question, la plupart des réponses ont évité cette question et a simplement dit de ne pas utiliser swizzling.

L'aide de la méthode de grésillement, c'est comme utiliser des couteaux dans la cuisine. Certaines personnes ont peur de couteaux aiguisés parce qu'ils pensent qu'ils vont se coupe mal, mais la vérité est que les couteaux pointus sont plus sûrs.

Méthode swizzling peut être utilisé pour écrire mieux, plus efficace, plus facile à gérer le code. Il peut également être maltraités et mener à d'horribles bugs.

Arrière-plan

Comme avec tous les modèles, si nous sommes pleinement conscients des conséquences de ce modèle, nous sommes en mesure de prendre des décisions plus éclairées quant à savoir si ou de ne pas l'utiliser. Les Singletons sont un bon exemple de quelque chose qui est assez controversée, et pour une bonne raison, ils sont vraiment difficiles à mettre en œuvre correctement. De nombreuses personnes choisissent d'utiliser des singletons. La même chose peut être dit à propos de swizzling. Vous devez former votre propre opinion une fois que vous comprenez parfaitement à la fois le bon et le mauvais.

Discussion

Voici quelques-uns des pièges de la méthode swizzling:

  • Méthode swizzling n'est pas atomique
  • Les changements de comportement de l'onu-le code de la propriété
  • Possible les conflits de noms
  • Swizzling modifications de la méthode des arguments
  • L'ordre de swizzles questions
  • Difficile à comprendre (regarde récursive)
  • Difficile à déboguer

Ces points sont tous valides, et en s'adressant à eux, nous pouvons améliorer notre compréhension de la méthode swizzling ainsi que la méthodologie utilisée pour atteindre le résultat. Je vais prendre chacun à un moment.

Méthode swizzling n'est pas atomique

Je n'ai pas encore de voir une mise en œuvre de la méthode swizzling c'est sûr à utiliser simultanément1. Ce n'est pas réellement un problème dans 95% des cas que vous voulez utiliser la méthode swizzling. Habituellement, vous voulez simplement de remplacer la mise en œuvre d'une méthode, et que vous souhaitez que la mise en œuvre à être utilisé pour l'ensemble de la durée de vie de votre programme. Cela signifie que vous devriez faire votre méthode swizzling en +(void)load. L' load méthode de la classe est exécutée en série au début de votre application. Vous n'aurez pas de problèmes avec la concurrence d'accès si vous faites votre swizzling ici. Si vous étiez à swizzle en +(void)initialize, cependant, vous pourriez vous retrouver avec une condition de concurrence dans votre swizzling la mise en œuvre et l'exécution pourrait se retrouver dans un drôle d'état.

Les changements de comportement de l'onu-le code de la propriété

C'est un problème de swizzling, mais c'est un peu le point de l'ensemble. L'objectif est d'être en mesure de modifier le code. La raison pour laquelle les gens ce point comme étant une grosse affaire est parce que vous n'êtes pas simplement de changer les choses pour la seule occurrence de NSButton que vous voulez changer les choses, mais au lieu de tous les NSButton des instances de votre application. Pour cette raison, vous devez être prudent lorsque vous swizzle, mais vous n'avez pas née à l'éviter complètement.

Pensez-y de cette façon... si vous substituez une méthode dans une classe et vous n'appelez pas la super méthode de classe, vous pouvez causer des problèmes surviennent. Dans la plupart des cas, la super-classe s'attend à ce que la méthode à appeler (sauf spécification contraire). Si vous appliquez cette même pensée pour swizzling, vous avez couvert la plupart des questions. Toujours téléphoner à l'origine de la mise en œuvre. Si vous ne le faites pas, vous êtes probablement de changer trop de choses à être en sécurité.

Possible les conflits de noms

Les conflits de noms sont un problème tout au long de Cacao. Nous avons souvent le préfixe des noms de classe et de la méthode dans les noms de catégories. Malheureusement, les conflits de noms sont un fléau dans notre langue. Dans le cas de swizzling, cependant, ils n'ont pas à l'être. Nous avons juste besoin de changer la façon de penser de la méthode swizzling légèrement. La plupart des swizzling est fait comme ceci:

@interface NSView : NSObject
- (void)setFrame:(NSRect)frame;
@end

@implementation NSView (MyViewAdditions)

- (void)my_setFrame:(NSRect)frame {
    // do custom work
    [self my_setFrame:frame];
}

+ (void)load {
    [self swizzle:@selector(setFrame:) with:@selector(my_setFrame:)];
}

@end

Cela fonctionne bien, mais qu'arriverait-il si my_setFrame: a été défini quelque part d'autre? Ce problème n'est pas unique à swizzling, mais on peut contourner cela de toute façon. La solution a l'avantage de répondre à d'autres pièges. Voici ce que nous faisons à la place:

@implementation NSView (MyViewAdditions)

static void MySetFrame(id self, SEL _cmd, NSRect frame);
static void (*SetFrameIMP)(id self, SEL _cmd, NSRect frame);

static void MySetFrame(id self, SEL _cmd, NSRect frame) {
    // do custom work
    SetFrameIMP(self, _cmd, frame);
}

+ (void)load {
    [self swizzle:@selector(setFrame:) with:(IMP)MySetFrame store:(IMP *)&SetFrameIMP];
}

@end

Bien que cela semble un peu moins comme Objective-C (puisque c'est à l'aide de pointeurs de fonction), il évite les conflits de noms. En principe, c'est de faire exactement la même chose que la norme swizzling. Cela peut être un peu de changement pour les personnes qui ont été à l'aide de swizzling comme il a été défini pour un certain temps, mais en fin de compte, je pense que c'est mieux. Le swizzling méthode est définie ainsi:

typedef IMP *IMPPointer;

BOOL class_swizzleMethodAndStore(Class class, SEL original, IMP replacement, IMPPointer store) {
    IMP imp = NULL;
    Method method = class_getInstanceMethod(class, original);
    if (method) {
        const char *type = method_getTypeEncoding(method);
        imp = class_replaceMethod(class, original, replacement, type);
        if (!imp) {
            imp = method_getImplementation(method);
        }
    }
    if (imp && store) { *store = imp; }
    return (imp != NULL);
}

@implementation NSObject (FRRuntimeAdditions)
+ (BOOL)swizzle:(SEL)original with:(IMP)replacement store:(IMPPointer)store {
    return class_swizzleMethodAndStore(self, original, replacement, store);
}
@end

Swizzling modifications de la méthode des arguments

C'est le grand dans mon esprit. Pour cette raison, la méthode standard swizzling ne devrait pas être fait. Vous sont en train de changer les arguments passés à la méthode originale de mise en œuvre. C'est là que ça se passe:

[self my_setFrame:frame];

Quelle ligne est la suivante:

objc_msgSend(self, @selector(my_setFrame:), frame);

Qui va utiliser le moteur d'exécution de regarder pour la mise en œuvre de l' my_setFrame:. Une fois la mise en œuvre est trouvé, il appelle à la mise en œuvre avec les mêmes arguments qui ont été donnés. La mise en œuvre qu'il trouve est à l'origine de la mise en œuvre de l' setFrame:, de sorte qu'il va de l'avant et les appels, mais l' _cmd argument n'est pas setFrame: comme il le devrait. Il est maintenant my_setFrame:. La mise en œuvre d'origine est appelé avec un argument il n'a jamais pensé que cela pourrait recevoir. Ce n'est pas bon.

Il y a une solution simple: utiliser l'alternative swizzling technique défini ci-dessus. Les arguments restent inchangées!

L'ordre de swizzles questions

L'ordre dans lequel les méthodes get swizzled questions. En supposant setFrame: n'est définie que sur NSView, imaginez cet ordre des choses:

[NSButton swizzle:@selector(setFrame:) with:@selector(my_buttonSetFrame:)];
[NSControl swizzle:@selector(setFrame:) with:@selector(my_controlSetFrame:)];
[NSView swizzle:@selector(setFrame:) with:@selector(my_viewSetFrame:)];

Ce qui se passe lorsque la méthode sur NSButton est swizzled? Bien plus swizzling fera en sorte qu'il n'est pas de remplacer la mise en œuvre de l' setFrame: pour tous les points de vue, de sorte qu'il va tirer vers le haut la méthode d'instance. Cela permettra d'utiliser la mise en œuvre de redéfinir setFrame: dans la NSButton classe de sorte que l'échange d'implémentations n'affecte pas tous les points de vue. L'implémentation existante est celle qui est définie sur NSView. La même chose va se produire lorsque swizzling sur NSControl (de nouveau à l'aide de l' NSView mise en œuvre).

Lorsque vous appelez setFrame: sur un bouton, il va donc appeler votre swizzled méthode, puis passer directement à l' setFrame: méthode initialement défini sur NSView. L' NSControl et NSView swizzled implémentations ne sera pas appelé.

Mais que faire si la commande:

[NSView swizzle:@selector(setFrame:) with:@selector(my_viewSetFrame:)];
[NSControl swizzle:@selector(setFrame:) with:@selector(my_controlSetFrame:)];
[NSButton swizzle:@selector(setFrame:) with:@selector(my_buttonSetFrame:)];

Depuis le point de vue swizzling prend place, en premier lieu, le contrôle swizzling sera en mesure de tirer vers le haut la bonne méthode. De même, depuis le contrôle de la swizzling était avant que le bouton swizzling, le bouton va tirer vers le haut le contrôle du swizzled mise en œuvre de l' setFrame:. C'est un peu déroutant, mais c'est l'ordre correct. Comment pouvons-nous nous assurer cet ordre des choses?

De nouveau, il suffit d'utiliser load de swizzle choses. Si vous swizzle en load et vous n'apportez des modifications à la classe en cours de chargement, vous serez en sécurité. L' load méthode garantit que la super-classe de la méthode de chargement sera appelée avant tout des sous-classes. Nous allons trouver le bon ordre!

Difficile à comprendre (regarde récursive)

En regardant un traditionnellement définie swizzled méthode, je pense que c'est vraiment difficile de dire ce qui se passe. Mais en regardant l'alternative que nous avons fait swizzling ci-dessus, il est assez facile à comprendre. Ce un a déjà été résolu!

Difficile à déboguer

L'un des confusions lors de la mise au point est de voir un étrange backtrace où la swizzled noms sont mélangés et tout devient confuse dans votre tête. Encore une fois, l'alternative de la mise en œuvre des adresses. Vous verrez clairement les fonctions nommées dans backtraces. Encore, swizzling peut être difficile à déboguer, car il est difficile de se rappeler quel est l'impact de la swizzling est d'avoir. Documenter votre code bien (même si vous pensez que vous êtes le seul à le voir). Suivre de bonnes pratiques, et vous serez bien. Ce n'est pas plus difficile à déboguer que le code multithread.

Conclusion

Méthode swizzling est sûr s'il est utilisé correctement. Une simple mesure de sécurité que vous pouvez prendre est de ne swizzle en load. Comme beaucoup de choses dans la programmation, il peut être dangereux, mais de comprendre les conséquences vont vous permettre de l'utiliser correctement.


1 à l'Aide de l'défini ci-dessus swizzling méthode, vous pourriez faire des choses "thread-safe" si vous utilisez les trampolines. Vous auriez besoin de deux trampolines. Au début de la méthode, vous devez affecter le pointeur de fonction, store, pour une fonction qui filé jusqu'à ce que l'adresse à laquelle store a souligné changé. Cela permettrait d'éviter toute situation de concurrence dans laquelle les swizzled méthode a été appelée avant vous étiez en mesure de définir l' store pointeur de fonction. Vous devez utiliser un trampoline dans le cas où la mise en œuvre n'est pas déjà défini dans la classe et ont le trampoline de recherche et d'appel de la super-classe de la méthode correctement. La définition de la méthode de sorte qu'il dynamiquement regarde la super mise en œuvre permettra de s'assurer que l'ordre de swizzling appels n'a pas d'importance.

11voto

Robert Points 10822

Je vais d'abord définir exactement ce que je veux dire par la méthode swizzling:

  • Re-routage de tous les appels qui ont été envoyés à une méthode (appelée) à une nouvelle méthode (appelée B).
  • Nous avons propre Méthode B
  • Nous n'avons pas propre méthode, d'Une
  • La méthode B effectue des travaux, puis appelle la méthode A.

Méthode swizzling est plus général que ça, mais c'est le cas, je suis intéressé.

Dangers:

  • Les changements dans la classe d'origine. Nous n'avons pas propre à la classe que nous sommes swizzling. Si les changements de classe de notre swizzle peut cesser de fonctionner.

  • Dur à maintenir. Non seulement avez-vous à écrire et à maintenir la swizzled méthode. vous avez à écrire et à maintenir le code de préformes la swizzle

  • Difficile à déboguer. Il est difficile de suivre le flux d'une swizzle, certaines personnes peuvent même ne pas réaliser le swizzle a été préformé. Si il y a des bugs introduits par la swizzle (peut-être cotisations à des changements dans la classe d'origine) ils seront difficiles à résoudre.

En résumé, vous devriez garder swizzling à un minimum et à examiner comment les changements dans la classe d'origine peut affecter votre swizzle. Aussi, vous devez clairement commentaire de document et de ce que vous faites (ou simplement de l'éviter entièrement).

7voto

Caleb Points 72897

Ce n'est pas le swizzling 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 du framework. C'est en supposant que vous savez quelque chose sur la façon dont ces cours privés de travail qui est "dangereux". Même si vos modifications à l'œuvre aujourd'hui, il ya toujours une chance que Apple va changer la classe, dans l'avenir, et la cause de votre modification à la pause. Aussi, si de nombreuses applications différentes de le faire, il rend beaucoup plus difficile pour Apple à modifier le cadre sans casser beaucoup de logiciels existants.

5voto

Arafangion Points 5650

Utilisé avec prudence et sagesse, il peut conduire à un code élégant, mais en général, il conduit à code source de confusion.

Je dis qu'il devrait être interdit, sauf si l'on sait qu'elle présente un très élégant possibilité pour un particulier des tâches de conception, mais vous avez besoin de savoir clairement pourquoi il s'applique bien à la situation, et pourquoi ces solutions de rechange ne travaillent pas à l'élégance de la situation.

Par exemple, une bonne application de la méthode swizzling est isa swizzling, qui est de savoir comment ObjC implémente la Valeur de la Clé de l'Observation.

Un mauvais exemple peut-être en s'appuyant sur la méthode swizzling comme un moyen d'augmenter vos classes, ce qui conduit à de très hautes couplage.

5voto

user3288724 Points 31

Bien que j'ai utilisé cette technique, je tiens à souligner que:

  • Il dissimule votre code, car il peut causer des nations unies-documentée, si désiré, des effets secondaires. Quand on lit le code, il/elle peut ne pas être conscients de l'effet secondaire d'un comportement qui est requis à moins qu'il/elle se souvient de recherche de la base de code pour voir si elle a été swizzled. Je ne suis pas sûr de la façon d'atténuer ce problème, car il n'est pas toujours possible de documenter tous les endroits où le code est dépendant de l'effet secondaire swizzled comportement.
  • Il peut rendre votre code moins réutilisable parce que quelqu'un qui trouve un segment de code qui dépend de la swizzled comportement qu'ils aimeraient utiliser ailleurs ne peut pas simplement copier et la coller dans une autre base de code sans aussi la recherche et la copie de la swizzled méthode.

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: