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.