Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Method Swizzling does not work

I would like to make use of method swizzling, but am unable to get even simple examples to work for me. It is possible that I am misunderstanding what the concept is, but as far as I know it allows for method implementations to be swapped.

Given two methods, A and B, I would like to swap their implementations such that calling A would execute B instead. I came across a few examples of swizzling (example1 and example2). I created a new project with a class to test this.

class Swizzle: NSObject
{
    func method()
    {
        print("A");
    }
}

extension Swizzle
{
    override class func initialize()
    {
        struct Static
        {
            static var token: dispatch_once_t = 0;
        }

        // make sure this isn't a subclass
        if (self !== Swizzle.self)
        {
            return;
        }

        dispatch_once(&Static.token)
        {
            let originalSelector = Selector("method");
            let swizzledSelector = Selector("methodExt");

            let originalMethod = class_getInstanceMethod(self, originalSelector);
            let swizzledMethod = class_getInstanceMethod(self, swizzledSelector);

            print(method_getImplementation(originalMethod));
            print(method_getImplementation(swizzledMethod));

            let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));

            if didAddMethod
            {
                class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
            }
            else
            {
                method_exchangeImplementations(originalMethod, swizzledMethod);
            }

            print(method_getImplementation(originalMethod));
            print(method_getImplementation(swizzledMethod));
        }
    }

    func methodExt()
    {
        print("B");
    }
}

I then try to execute it with

var s = Swizzle();
s.method();

The expected output is "B", but "A" is still being printed. As you can see from my code, I've included prints of each IMP before and after the swizzle operation. These prints show that the exchange does take place, yet the output remains the same.

Output:

0x000000010251a920
0x000000010251ad40
0x000000010251ad40
0x000000010251a920
A

Is there anything I am missing when it comes to getting these changes to take effect?

PS. Currently using XCode 7.0.1

like image 864
Kevin Avatar asked Oct 13 '15 07:10

Kevin


1 Answers

The issue is that your method() lacks the dynamic directive:

class Swizzle: NSObject
{
    dynamic func method()
    {
        print("A")
    }
}

Modify the declaration and it should work.

When using method swizzling in Swift there are two requirements that your classes/methods must comply with:

  • Your class must extend NSObject
  • The functions you want to swizzle must have the dynamic attribute

For a complete explanation of why this is required, check out Using Swift with Cocoa and Objective-C:

Requiring Dynamic Dispatch

While the @objc attribute exposes your Swift API to the Objective-C runtime, it does not guarantee dynamic dispatch of a property, method, subscript, or initializer. The Swift compiler may still devirtualize or inline member access to optimize the performance of your code, bypassing the Objective-C runtime. When you mark a member declaration with the dynamic modifier, access to that member is always dynamically dispatched. Because declarations marked with the dynamic modifier are dispatched using the Objective-C runtime, they’re implicitly marked with the @objc attribute.

Requiring dynamic dispatch is rarely necessary. However, you must use the dynamic modifier when you know that the implementation of an API is replaced at runtime. For example, you can use the method_exchangeImplementations function in the Objective-C runtime to swap out the implementation of a method while an app is running. If the Swift compiler inlined the implementation of the method or devirtualized access to it, the new implementation would not be used.

Swift 3 Update:

There have been a few changes in regard to GCD and dispatch_once is not available anymore. To perform the same one time operation, we can enclose the code in the initialization block of a global static class constant.

The Swift language guarantees that this code will be executed only once during the lifetime of the application.

class TestSwizzling : NSObject {
    dynamic func methodOne()->Int{
        return 1
    }
}

extension TestSwizzling {

    //In Objective-C you'd perform the swizzling in load(), 
    //but this method is not permitted in Swift
    override class func initialize()
    {

        struct Inner {
            static let i: () = {

                let originalSelector = #selector(TestSwizzling.methodOne)
                let swizzledSelector = #selector(TestSwizzling.methodTwo)                 
                let originalMethod = class_getInstanceMethod(TestSwizzling.self, originalSelector);
                let swizzledMethod = class_getInstanceMethod(TestSwizzling.self, swizzledSelector)                
                method_exchangeImplementations(originalMethod, swizzledMethod)
            }
        }
        let _ = Inner.i
    }

    func methodTwo()->Int{
        // It will not be a recursive call anymore after the swizzling
        return methodTwo()+1
    }
}

var c = TestSwizzling()
print(c.methodOne())
print(c.methodTwo())

Swift 2.2 Update:

I've updated the original example for the new #selector attribute:

class TestSwizzling : NSObject {
    dynamic func methodOne()->Int{
        return 1
    }
}

extension TestSwizzling {

    //In Objective-C you'd perform the swizzling in load(), 
    //but this method is not permitted in Swift
    override class func initialize()
    {
        struct Static
        {
            static var token: dispatch_once_t = 0
        }

        // Perform this one time only
        dispatch_once(&Static.token)
        {
                let originalSelector = #selector(TestSwizzling.methodOne)
                let swizzledSelector = #selector(TestSwizzling.methodTwo)                 
                let originalMethod = class_getInstanceMethod(self, originalSelector);
                let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)                
                method_exchangeImplementations(originalMethod, swizzledMethod)
        }
    }

    func methodTwo()->Int{
        // It will not be a recursive call anymore after the swizzling
        return methodTwo()+1
    }
}

var c = TestSwizzling()
print(c.methodOne())
print(c.methodTwo())

If you need an example to play with, check out this sample project on github.

like image 85
uraimo Avatar answered Sep 21 '22 22:09

uraimo