Method Swizzling en Objective-C

ChachaChacha 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:
<br />@interface NSString (ExtensionDebile)<br />-(int) length;<br />@end<br /><br />@implementation NSString(ExtensionDebile)<br />-(int) length<br />{<br /> return 0; //impossible d&#39;avori la longueur d&#39;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 &amp;&amp; replacement_method)<br />{<br />  char *temp1;<br />  IMP temp2;<br /><br />  temp1 = original_method-&gt;method_types;<br />  original_method-&gt;method_types = replacement_method-&gt;method_types;<br />  replacement_method-&gt;method_types = temp1;<br /><br />  temp2 = original_method-&gt;method_imp;<br />  original_method-&gt;method_imp = replacement_method-&gt;method_imp;<br />  replacement_method-&gt;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&#39;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

Réponses

  • wiskywisky Membre
    22:03 modifié #2
    cela permettrait d'avoir un seul bouton pour 2 actions?
  • ChachaChacha Membre
    22:03 modifié #3
    dans 1123684356:

    cela permettrait d'avoir un seul bouton pour 2 actions?

    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)
  • veveveve Membre
    22:03 modifié #4
    c'est quoi (type) original_method et replacement_method ? :(
  • ChachaChacha Membre
    août 2005 modifié #5
    dans 1123704620:

    c'est quoi (type) original_method et replacement_method ? :(

    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
Connectez-vous ou Inscrivez-vous pour répondre.