Method Swizzling en Objective-C
Chacha
Membre
Oui, je sais, le titre n'est pas très explicite.
J'ai trouvé ce document ici : http://www.cocoadev.com/index.pl?MethodSwizzling
Introduction
Vous connaissez les "catégories" en objective-C, qui permettent d'étendre des classes.
Vous savez qu'au lieu d'étendre, on peut carrément surcharger une méthode pour la remplacer complètement. En gros, vous pouvez intercepter un appel à n'importe quelle méthode en Cocoa, même celles de l'AppKit que vous n'avez pas faites vous-même, et les remplacer.
Exemple:
Bon, c'est bien beau, mais on écrase ainsi le comportement initial. Comment le retrouver ?
Voilà , apparemment, c'est moins bien que du sous-classage, puisqu'on ne peut pas appeller le code original.
Mais ça, c'est sans faire appel au méthode swizzling!
Le "method swizzling"
Objective-C est dynamique, rien ne vous empêche d'intervertir les adresses mémoire de deux méthodes !
Reprenons mon exemple :
Du coup, maintenant, un appel à -(int) length est remplacé par un appel à -(int) lengthDebile, *et vice-versa*.
1) vous avez bien "remplacé" le code de length
2) mais le code original est toujours accessible !
Conclusion
Pour faire les choses bien, on se fait une petite fonction "swizzle" qui échange les selector de n'importe quelle classe passée en argument. On peut aussi traiter le cas des méthodes de classe.
Je ne vous cache pas que ça fait un peu "technique de porc", mais c'est bougrement puissant.
+
Chacha
J'ai trouvé ce document ici : http://www.cocoadev.com/index.pl?MethodSwizzling
Introduction
Vous connaissez les "catégories" en objective-C, qui permettent d'étendre des classes.
Vous savez qu'au lieu d'étendre, on peut carrément surcharger une méthode pour la remplacer complètement. En gros, vous pouvez intercepter un appel à n'importe quelle méthode en Cocoa, même celles de l'AppKit que vous n'avez pas faites vous-même, et les remplacer.
Exemple:
<br />@interface NSString (ExtensionDebile)<br />-(int) length;<br />@end<br /><br />@implementation NSString(ExtensionDebile)<br />-(int) length<br />{<br /> return 0; //impossible d'avori la longueur d'une chaà®ne. hahaha.<br />}<br />@end<br />
Bon, c'est bien beau, mais on écrase ainsi le comportement initial. Comment le retrouver ?
<br />@implementation NSString(ExtensionDebile)<br />-(int) length<br />{<br />Â int resultatNormal = [self length]; //ben non, récursif, ça plante.<br />Â int resultatNormal2 = [super length];//non plus, super est NSObject<br />Â return 0;//resultat idiot<br />}<br />@end<br />
Voilà , apparemment, c'est moins bien que du sous-classage, puisqu'on ne peut pas appeller le code original.
Mais ça, c'est sans faire appel au méthode swizzling!
Le "method swizzling"
Objective-C est dynamique, rien ne vous empêche d'intervertir les adresses mémoire de deux méthodes !
Reprenons mon exemple :
<br />@interface NSString (ExtensionDebile)<br />-(int) lengthDebile; //on ne met pas le même nom<br />@end<br /><br />@implementation NSString (ExtensionDebile)<br />-(int) lengthDebile<br />{<br />Â return 0;<br />}<br />@end<br /><br />//et quelque part dans le code<br />original_method = class_getInstanceMethod([NSString class], @selector(length));<br />replacement_method = class_getInstanceMethod([NSString class], @selector(lengthDebile));<br />if (original_method && replacement_method)<br />{<br />Â char *temp1;<br />Â IMP temp2;<br /><br />Â temp1 = original_method->method_types;<br />Â original_method->method_types = replacement_method->method_types;<br />Â replacement_method->method_types = temp1;<br /><br />Â temp2 = original_method->method_imp;<br />Â original_method->method_imp = replacement_method->method_imp;<br />Â replacement_method->method_imp = temp2;<br />}<br />
Du coup, maintenant, un appel à -(int) length est remplacé par un appel à -(int) lengthDebile, *et vice-versa*.
1) vous avez bien "remplacé" le code de length
2) mais le code original est toujours accessible !
<br />@implementation NSString (ExtensionDebile)<br />-(int) lengthDebile<br />{<br />Â int resultatNormal = [self lengthDebile];//et non, ce n'est pas récursif !<br />Â return 0;<br />}<br />@end<br />
Conclusion
Pour faire les choses bien, on se fait une petite fonction "swizzle" qui échange les selector de n'importe quelle classe passée en argument. On peut aussi traiter le cas des méthodes de classe.
Je ne vous cache pas que ça fait un peu "technique de porc", mais c'est bougrement puissant.
+
Chacha
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
Euh... Oui, je pense, mais il faut dans ce cas prévoir d'échanger les méthodes au bon moment. De toutes manières, c'est un truc pas bien catholique, donc mieux vaut le réserver à des cas très très précis. (Qui a dit patcher iChat ? http://www.objective-cocoa.org/forum/index.php?topic=1072.msg11749#msg11749)
Je pense que le IMP représente en quelque sorte l'accès au code (adresse mémoire), tandis que le "types", un char*, désigne sous forme de chaà®ne de caractères les types de retour et des arguments du selector.
En effet, utilisons l'utilitaire otool de la ligne de commande :
$>otool -ov /Applications/iChat.app/Contents/MacOS/iChat
on obtient par exemple :
_setVCTextureBuffer:isFreezeFrame:
method_types 0x001dc028 v40@0:4{?=*iiiiii}8c36
le method_types indique que la méthode _setVCTextureBuffer:isFreezeFrame
retourn void (v40@0)
prend en paramètre une structure composée d'un pointeur et 6 entiers
(*iiiiii)
et un BOOL (8c36)
En fait, je dis ça, mais ce ne sont que des déductions que j'ai faites en observant le résultat du otool, et de ceci, que j'ai lu quand j'ai appris l'Objective-C:
http://developer.apple.com/documentation/Cocoa/Conceptual/ObjectiveC/RuntimeOverview/chapter_4_section_6.html#//apple_ref/doc/uid/20001425-113054
[edit]
D'ailleurs, en relisant la doc citée, je me rends compte que me suis pas mal lourdé dans mes déductions ;-)
[/edit]
+
Chacha