3

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!

hippietrail
  • 15,848
  • 18
  • 99
  • 158
Kunal Shah
  • 1,071
  • 9
  • 24
  • What do you mean by "there's no access to `self` within the swizzled method" ? Are you referring to the static method on SwizzingHelper? What are you expecting to be accessible via `self` that isn't? – WongWray Jan 07 '22 at 02:14
  • In the examples that I've seen, `self` refers to the instance that's being swizzled. But since I'm using a helper class and not an extension of the class that's being swizzled, I cannot use `self` to refer to the swizzled class' instance. – Kunal Shah Jan 07 '22 at 11:05
  • Does this answer your question? [How to swizzle a method of a private class](https://stackoverflow.com/questions/22361427/how-to-swizzle-a-method-of-a-private-class) – Willeke Jan 07 '22 at 11:46
  • Self refers to whatever receiver the method was called on. It doesn't really "know" anything about the class where the method implementation is defined. It just happens to be that in most cases, the type of `self` happens to be the defining class (or a subclass thereof). But I the wild west of runtime hacking, anything is possible :p – Alexander Jan 12 '22 at 22:08

1 Answers1

0

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()
WongWray
  • 2,414
  • 1
  • 20
  • 25
  • > It seems like you could just pass in a block that runs the framework's method after running your own logic. This isn't possible in my case. I'm looking at intercepting some video player events that are opened from a `WKWebView`. So my code doesn't actually have access to the `AVPlayerViewController` that the WKWebView opened from video embeds. – Kunal Shah Jan 12 '22 at 20:30
  • Definitely an interesting use-case. Hope the answer helped! – WongWray Jan 12 '22 at 21:30
  • 1
    First of all, thanks for your answer! I tried it and it didn't work, unfortunately :( I defined `originalMethod` as an optional and inside my swizzled method (your `run` method), when I try to unwrap `originalMethod`, it crashes. While the swizzled method is called, `self` refers to an instance of the swizzled class (the one in the private framework), and it doesn't have a property called `originalMethod` which I suspect is why it crashes? In the Xcode debugger, when the run method is called, the memory address of `originalMethod` is `0x0000000000000000`. – Kunal Shah Jan 12 '22 at 21:37