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 theSelector
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() ***
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.
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!
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.