How to use runtime in Swift?

I want to replace viewDidLoad with the swizzling_viewDidLoad method. What should I do?

I'm not an expert, but I think the only extras in Swift are:

  • import ObjectiveC if it isn't implicitly imported;
  • use @objc dynamic on the methods to be swizzled;
  • use #selector to get the Selector arguments.
import Foundation
import ObjectiveC
import UIKit

class Test: UIViewController {

    class func swizzleViewDidLoadMethods() {
        let oldSelector = #selector(viewDidLoad)
        let newSelector = #selector(swizzling_viewDidLoad)
        let oldMethod = class_getInstanceMethod(self, oldSelector)!
        let newMethod = class_getInstanceMethod(self, newSelector)!
        let oldImplementation = method_getImplementation(oldMethod)
        let newImplementation = method_getImplementation(newMethod)
        _ = method_setImplementation(oldMethod, newImplementation)
        _ = method_setImplementation(newMethod, oldImplementation)
    }

    @objc dynamic override func viewDidLoad() {
        super.viewDidLoad()
        print("*** OLD \(#function) ***")
    }

    @objc dynamic func swizzling_viewDidLoad() {
        print("*** NEW \(#function) ***")
    }
}
let test = Test()
test.viewDidLoad()                     // *** OLD viewDidLoad() ***
test.swizzling_viewDidLoad()           // *** NEW swizzling_viewDidLoad() ***

Test.swizzleViewDidLoadMethods()
test.viewDidLoad()                     // *** NEW swizzling_viewDidLoad() ***
test.swizzling_viewDidLoad()           // *** OLD viewDidLoad() ***

Test.swizzleViewDidLoadMethods()
test.viewDidLoad()                     // *** OLD viewDidLoad() ***
test.swizzling_viewDidLoad()           // *** NEW swizzling_viewDidLoad() ***

Test.swizzleViewDidLoadMethods()
test.viewDidLoad()                     // *** NEW swizzling_viewDidLoad() ***
test.swizzling_viewDidLoad()           // *** OLD viewDidLoad() ***
1 Like

Does anyone know the best way to call the original method from its replacement?

Should the replacement call "itself", which could be confused for infinite recursion?

@objc dynamic func swizzling_viewDidLoad() {
    swizzling_viewDidLoad()
}

Or should the original implementation be saved to a third method?

@objc dynamic func swizzling_viewDidLoad() {
    original_viewDidLoad()
}

Cc: @davedelong

Your replacement appears to call itself (swizzling_viewDidLoad()), assuming you're using method_exchangeImplementations().


Edited to add: you're using two calls to method_setImplementation(). That'll work, but it'll be shorter to do it in a single method_exchangeImplementations() call.

1 Like

You can generalize that and wrap it up into an extension on NSObject (maybe even NSObjectProtocol?)

import Foundation 

extension NSObject {
    class func swizzle(_ oldSelector: Selector, with newSelector: Selector) {
        let oldMethod = class_getInstanceMethod(self, oldSelector)!
        let newMethod = class_getInstanceMethod(self, newSelector)!
        let oldImplementation = method_getImplementation(oldMethod)
        let newImplementation = method_getImplementation(newMethod)
        _ = method_setImplementation(oldMethod, newImplementation)
        _ = method_setImplementation(newMethod, oldImplementation)
    }
}

Sample usage:

class C: NSObject {
    @objc dynamic func foo() {
        print("foo!")
    }
    
    @objc dynamic func bar() {
        print("bar!")
    }
}

let obj = C()
obj.foo() // Pre-swizzle, prints: foo!
obj.bar() // Pre-swizzle, prints: bar!

C.swizzle(#selector(C.foo), with: #selector(C.bar))

obj.foo() // Post-swizzle, prints: bar!
obj.bar() // Post-swizzle, prints: foo!
1 Like

If the swizzling_viewDidLoad method is also @objc dynamic, then the call to "itself" should use objc_msgSend, so it will hopefully end up at the original viewDidLoad implementation.