Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling original function from swizzled function

I am messing around with method swizzling and would like to call the original function after performing a method_exchangeImplementations. I have two projects I have setup for this.

The first project is the main project for the application. This project includes all of the logic for the application. Notice that originalMethodName is called when the view loads.

@implementation ViewController

- (void)originalMethodName 
{
    NSLog(@"REAL %s", __func__);
}

- (void)viewDidLoad {
    [super viewDidLoad];

    NSLog(@"REAL %s", __func__);

    [self originalMethodName];
}

@end

The second project includes only the code for swizzling. I have a method swizzle_originalMethodName which includes the code I want to inject into the main application with the originalMethodName function is called.

@implementation swizzle_ViewController

- (void)swizzle_originalMethodName
{
    NSLog(@"FAKE %s", __func__);
}

__attribute__((constructor)) static void initializer(void)
{
    NSLog(@"FAKE %s", __func__);

    Class c1 = objc_getClass("ViewController");
    Class c2 = [swizzle_ViewController class];
    Method m1 = class_getInstanceMethod(c1, @selector(originalMethodName));
    Method m2 = class_getInstanceMethod(c2, @selector(swizzle_originalMethodName));
    method_exchangeImplementations(m1, m2);
}

@end

The swizzle is working just fine (as seen in the output below), but now I want to be able to call originalMethodName from the swizzle_originalMethodName

2016-08-17 14:18:51.765 testMacOS[7295:1297055] FAKE initializer
2016-08-17 14:18:51.822 testMacOS[7295:1297055] REAL -[ViewController viewDidLoad]
2016-08-17 14:18:51.822 testMacOS[7295:1297055] FAKE -[swizzle_ViewController swizzle_originalMethodName]

I have tried to use NSInvocation but am not having any luck. Any ideas what I am doing wrong?

Class c1 = objc_getClass("ViewController");
Method m1 = class_getInstanceMethod(c1, @selector(originalMethodName));
NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:method_getTypeEncoding(    m1)];
NSInvocation *originalInvocation = [NSInvocation invocationWithMethodSignature:methodSignature];
[originalInvocation invoke];
like image 489
Corey Gagnon Avatar asked Aug 17 '16 18:08

Corey Gagnon


2 Answers

If you are swizzling within a class hierarchy, e.g. you have a subclass which swizzles one of its ancestors methods with one of its own, then you simply have the swizzled-in method apparently call itself – that call will actually call the swizzled-out method as the methods have been swapped. In your case you would have:

- (void)swizzle_originalMethodName
{
   NSLog(@"FAKE %s", __func__);
   [self swizzle_originalMethodName]; // call original
}

This does not work in your case as you are cross-class swizzling, so self doesn't reference the class with the swizzled-out method. And you don't have an instance of the swizzling class you can call the swizzled-out method on...

Here is one easy way to fix this, what your swizzle-in method needs to be able to do is call the original implementation, and you can get that when you setup the swizzling.

In Objective-C a method is implemented by a function whose first two arguments are the object reference the method is being called on and the selector and the remaining arguments are those of the method. For example the NSString method:

- (NSRange)rangeOfString:(NSString *)aString

is implemented by a function something like:

NSRange rangeOfStringImp(NSString *self, SEL cmd, NSString *aString)

You can obtain a function pointer to this implementation function using method_getImplementation.

To your code, first in your swizzle_ViewController declare a type for the implementation function of the method you are swizzling, and a global to store the function pointer:

typedef void (*OriginalImpType)(id self, SEL selector);
static OriginalImpType originalImp;

Now in your initializer method you need to save the method implementation, you can do this by adding the line shown:

Method m1 = class_getInstanceMethod(c1, @selector(originalMethodName));
originalImp = (OriginalImpType)method_getImplementation(m1); // save the IMP of originalMethodName

Finally have your swizzled-in method call the saved implementation:

- (void)swizzle_originalMethodName
{
   NSLog(@"FAKE %s", __func__);
   originalImp(self, @selector(originalMethodName)); // call the original IMP with the correct self & selector
}

Optional: The above works correctly, however it does a little more than is required – the method implementations are both exchanged and one is stored in a global variable, all you really need to do is save the original implementation of m1 and then set its implementation to that of m2. You can address this by replacing the call to method_exchangeImplementations with:

method_setImplementation(m1, method_getImplementation(m2));

It is a little more typing, but somewhat clearer as to what actually needs to be done.

HTH

like image 174
CRD Avatar answered Sep 26 '22 23:09

CRD


There is a slightly easier option to call the original implementation that doesn't require you to store the method implementation directly. When you exchange implementations of the methods, the original implementation will be stored in the swizzler class. You can fetch the swizzled out implementation using the class_getMethodImplementation function. Here is a playground sample:

import Cocoa

let fooSelector = Selector("fooWithArg:")
let swizzledFooSelector = Selector("swizzled_fooWithArg:")

class A: NSObject {
    @objc dynamic func foo(arg: String) {
        print("Foo \(arg) in A")
    }
}

class B: NSObject {
    private typealias FooFunc = @convention(c) (AnyObject, Selector, String) -> Void
    @objc func swizzled_foo(arg: String) {
        print("Swizzled_foo \(arg) in B")

        unsafeBitCast(
            class_getMethodImplementation(B.self, swizzledFooSelector),
            to: FooFunc.self
        )(self, fooSelector, arg)
    }
}

method_exchangeImplementations(
    class_getInstanceMethod(A.self, fooSelector)!,
    class_getInstanceMethod(B.self, swizzledFooSelector)!
)

A().foo(arg: "bar")

Calling the swizzled out implementation

like image 42
Timofey Solonin Avatar answered Sep 22 '22 23:09

Timofey Solonin