Objective-C Language Augmenter les méthodes en utilisant Method Swizzling


Exemple

Le runtime Objective-C vous permet de modifier l'implémentation d'une méthode à l'exécution. Ceci est appelé méthode swizzling et est souvent utilisé pour échanger les implémentations de deux méthodes. Par exemple, si les méthodes foo et bar sont échangées, l'envoi du message foo va maintenant exécuter l'implémentation de bar et vice versa.

Cette technique peut être utilisée pour augmenter ou "patcher" les méthodes existantes que vous ne pouvez pas éditer directement, telles que les méthodes des classes fournies par le système.

Dans l'exemple suivant, la méthode -[NSUserDefaults synchronize] est augmentée pour imprimer l'heure d'exécution de l'implémentation d'origine.

IMPORTANT: beaucoup de gens essaient de faire des swizzling en utilisant method_exchangeImplementations . Cependant, cette approche est dangereuse si vous devez appeler la méthode que vous remplacez, car vous l'appellerez avec un sélecteur différent de celui attendu. Par conséquent, votre code peut se révéler étrange et inattendu, en particulier si plusieurs utilisateurs manipulent un objet de cette manière. Au lieu de cela, vous devriez toujours faire un swizzling en utilisant setImplementation conjointement avec une fonction C, vous permettant d'appeler la méthode avec le sélecteur d'origine.

#import "NSUserDefaults+Timing.h"
#import <objc/runtime.h> // Needed for method swizzling

static IMP old_synchronize = NULL;

static void new_synchronize(id self, SEL _cmd);

@implementation NSUserDefaults(Timing)

+ (void)load
{
    Method originalMethod = class_getInstanceMethod([self class], @selector(synchronize:));
    IMP swizzleImp = (IMP)new_synchronize;
    old_synchronize = method_setImplementation(originalMethod, swizzleImp);
}
@end

static void new_synchronize(id self, SEL _cmd);
{
    NSDate *started;
    BOOL returnValue;

    started = [NSDate date];

    // Call the original implementation, passing the same parameters
    // that this function was called with, including the selector.
    returnValue = old_synchronize(self, _cmd);


    NSLog(@"Writing user defaults took %f seconds.", [[NSDate date] timeIntervalSinceDate:started]);

    return returnValue;
}

@end

Si vous devez modifier une méthode qui prend des paramètres, vous devez simplement les ajouter en tant que paramètres supplémentaires à la fonction. Par exemple:

static IMP old_viewWillAppear_animated = NULL;
static void new_viewWillAppear_animated(id self, SEL _cmd, BOOL animated);

...

Method originalMethod = class_getClassMethod([UIViewController class], @selector(viewWillAppear:));
IMP swizzleImp = (IMP)new_viewWillAppear_animated;
old_viewWillAppear_animated = method_setImplementation(originalMethod, swizzleImp);

...

static void new_viewWillAppear_animated(id self, SEL _cmd, BOOL animated)
{
    ...

    old_viewWillAppear_animated(self, _cmd, animated);

    ...
}