Swift - Method swizzling for private framework's method and then invoking original implementation

1.1k Views Asked by At

I'm trying to swizzle a private framework's method to perform some custom logic and then would like to call the original implementation.

Code:

class SwizzlingHelper {
    private struct Constants {
        static let privateFrameworkClassName = "privateFrameworkClassName"
        static let swizzledMethodSignature = "swizzledMethodSignature:"
    }

    static func swizzle() {
        let originalSelector = NSSelectorFromString(Constants.swizzledMethodSignature)
        if let swizzlingClass: AnyClass = NSClassFromString(Constants.privateFrameworkClassName),
           let originalMethod = class_getInstanceMethod(swizzlingClass.self, originalSelector),
           let swizzledMethod = class_getClassMethod(SwizzlingHelper.self, #selector(swizzledMethod)) {
            method_exchangeImplementations(originalMethod, swizzledMethod)
        } 
    }

    @objc
    private static func swizzledMethod(_ arg: String) {
        // Custom logic
        // Call original method here - how?
    }
}

I've seen several examples of how swizzling is done by extending a class, and then invoking the original implementation by calling self.originalImplementation() inside the swizzled method. Since this is a private framework's class, I cannot extend it and hence the SwizzlingHelper class helps assist with the swizzling. However, there's no access to self within the swizzled method to call the original implementation.

Any leads will be appreciated. Thanks!

1

There are 1 best solutions below

3
WongWray On

I'm not really seeing the purpose of swizzling in this scenario. If you're wanting to run the original method's logic in the end, then swizzling doesn't make much sense to me. It seems like you could just pass in a block that runs the framework's method after running your own logic.

With that said, I came up with this:

typealias PrivateClassFunction = @convention(c) () -> Void
class SwizzingHelper {
    private let originalMethod: PrivateClassFunction
    init(forClass: String, methodSignature: String) {
        let originalSelector: Selector = NSSelectorFromString(methodSignature)
        guard
            let swizzledClass = NSClassFromString(forClass),
            let swizzledMethod = class_getInstanceMethod(SwizzingHelper.self, #selector(run)),
            let originalMethod = class_getInstanceMethod(swizzledClass.self, originalSelector)
        else { fatalError() }
        self.originalMethod = unsafeBitCast(originalMethod, to: PrivateClassFunction.self)
        method_exchangeImplementations(originalMethod, swizzledMethod)
    }

    @objc
    func run() {
        print("Running new logic")
        originalMethod()
    }
}

Passing in the class name and selector strings just makes it a little more flexible in case you want to swizzle other methods/classes in the future. You could also come up with some way to make the PrivateClassFunction more variable as well (as it stands, you'll want to adjust that to match the parameters and return type of your private framework's methods).

You'll run the code like so:

let swizzler = SwizzingHelper(forClass: "PrivateClass", methodSignature: "privateClassMethodWithArg:")
swizzler.run()