Search code examples
iosobjective-cswiftisa-swizzling

Swift - method swizzling


This is the method swizzling code written in Objective-C. I am having a hard time converting this in Swift.

void MPApplicationDidRegisterForRemoteNotificationsWithDeviceToken(id self, SEL _cmd, UIApplication *application, NSData *deviceToken) {
    [[MPPush shared] appRegisteredForRemoteNotificationsWithDeviceToken:deviceToken];

    IMP original = [MPAppDelegateProxy originalImplementation:_cmd class:[self class]];
    if (original)
        ((void(*)(id, SEL, UIApplication *, NSData*))original)(self, _cmd, application, deviceToken);
}  

Swiftify isn't converting the above code correctly.

I tried to do this but I am not sure how to pass parameters and use the exact above parameters in Swift swizzling method. This is my fail attempt to convert the above in Swift (code doesn't even compile) :

var MPApplicationDidRegisterForRemoteNotificationsWithDeviceToken: Void {
        //     TODO:   MPPush.shared.app

        let original = MPAppDelegateProxy.proxyAppDelegate.originalImplementation(selector: cmd, forClass: type(of: self))
    }(self: Any, _cmd: Selector, application: UIApplication, deviceToken: Data)

Solution

  • Extend your class:

    extension YourClassName {
        static let classInit: () -> () = {
            let originalSelector = #selector(originalFunction)
            let swizzledSelector = #selector(swizzledFunction)
            swizzle(YourClassName.self, originalSelector, swizzledSelector)
        }
        
        @objc func swizzledFunction() {
            //Your new implementation
        }
    }
    

    Your class (YourClassName) should inherit from NSObject and originalSelector should be a dynamic method.

    swizzle is a closure that exchanges the implementations:

    private let swizzle: (AnyClass, Selector, Selector) -> () = { fromClass, originalSelector, swizzledSelector in
        guard 
            let originalMethod = class_getInstanceMethod(fromClass, originalSelector),
            let swizzledMethod = class_getInstanceMethod(fromClass, swizzledSelector)
            else { return }
        method_exchangeImplementations(originalMethod, swizzledMethod)
    }
    

    You could define swizzle in the class where you are going to do the swizzling. For example the AppDelegate class definition. And then do the swizzling in AppDelegate.init():

    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
        override init() {
            super.init()
            YourClassName.classInit()
        }
    }
    

    Example

    Here is a concrete example of swizzling, it exchanges the implementation of two methods that take one argument:

    class Example: NSObject {
        @objc dynamic func sayHi(to name: String) {
            print("Hi", name)
        }
    }
    
    extension Example {
        public class func swizzleMethod() {
            guard
                let originalMethod = class_getInstanceMethod(Example.self, #selector(Example.sayHi(to:))),
                let swizzledMethod = class_getInstanceMethod(Example.self, #selector(Example.sayHello(to:)))
                else { return }
            method_exchangeImplementations(originalMethod, swizzledMethod)
        }
        @objc func sayHello(to name: String) {
            print("Hello", name)
        }
    }
    
    Example.swizzleMethod()
    
    let a = Example()
    a.sayHi(to: "Nitish")        //Hello Nitish
    

    Swift Native swizzling

    As of Swift 5.1, there is a native version of method swizzling that does not rely on Objective-C’s message passing. The @_dynamicReplacement modifier can be used upon a replacement function, and takes as an argument the name of the function that it should replace. The function that is being replaced must be marked with the @dynamic modifier, unless -enable-implicit-dynamic compilation flag is used, which makes the compiler assume that every eligible entity has been marked with the modifier.

    For example:

    dynamic func original() {
        print("I am the original")
    }
    
    @_dynamicReplacement(for: original)
    func replacement() {
        print("I am the replacement")
    }
    
    original() // prints "I am the replacement"
    

    For more details on dynamic method replacement, visit this Swift forum page.