How do I implement method swizzling?

2019-02-01 07:21发布

问题:

I am trying to modify behaviour of a program (i dont have it's source) using SIMBL. I used class dump and found out that I need to overide an instance method

This method is in the class called controller. All I need to do is get the argument arg1 and thats it. Maybe NSLog it or post a notification... I read about method swizzling in objective-c but how can I use it?. I would need to refer to the class MessageController whose course i don't have.

Thanks!

回答1:

I'm guessing you need to call the original implementation after doing your NSLog; if not, you may be able to just use a category on the class to override the method.

To swizzle the method, first you need a replacement method. I usually put something like this in a category on the target class:

- (void)replacementReceiveMessage:(const struct BInstantMessage *)arg1 {
    NSLog(@"arg1 is %@", arg1);
    [self replacementReceiveMessage:arg1];
}

This looks like it will recursively call itself, but it won't because we're going to swap things around so calling ReceiveMessage: calls this method while calling replacementReceiveMessage: calls the old version.

The second step is to use the runtime functions to actually perform the swap. The advantage of using a category is that you can use load in the category to do the work:

+ (void)load {
    SEL originalSelector = @selector(ReceiveMessage:);
    SEL overrideSelector = @selector(replacementReceiveMessage:);
    Method originalMethod = class_getInstanceMethod(self, originalSelector);
    Method overrideMethod = class_getInstanceMethod(self, overrideSelector);
    if (class_addMethod(self, originalSelector, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod))) {
            class_replaceMethod(self, overrideSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    } else {
            method_exchangeImplementations(originalMethod, overrideMethod);
    }
}

There are two cases that need to be handled:

  • If the method we're swizzling is actually defined in a superclass, we have to use class_addMethod to add an implementation of ReceiveMessage: to the target class, which we do using our replacement implementation. Then we can use class_replaceMethod to replace replacementReceiveMessage: with the superclass's implementation, so our new version will be able to correctly call the old.
  • If the method is defined in the target class, class_addMethod will fail but then we can use method_exchangeImplementations to just swap the new and old versions.


回答2:

The library jrswizzle handles it. Doing it yourself is not recommended, because there are a lot of details to get right. (See the table documenting the failures of previous implementations in the jrswizzle readme.)

Say you have class like this:

@interface Weh : NSObject
-(void)foo;
-(void)bar;
@end

@implementation Weh
-(void)foo {
    NSLog(@"Foo called");
}
-(void)bar {
    NSLog(@"Bar called");
    [self bar];
}
@end

You can use it like this:

Weh *weh = Weh.new;
[weh foo];
[Weh jr_swizzleMethod:@selector(foo) withMethod:@selector(bar) error:nil];
[weh foo]; 

Output:

Foo called
Bar called
Foo called