Greetings, traveler!
Today, I propose to discuss rarely used techniques, one of which is Method Swizzling.
Method Swizzling allows you to replace your method directly in runtime while leaving the original implementation available.
We will consider an option where we want to customize the navigation bar’s appearance in the viewWillAppear method of the UIViewController. We will replace its implementation with swizzling to avoid doing this every time.
Let’s consider a scenario where we have a dedicated manager responsible for configuring the navigation bar.
struct NavigationBarManager {
static func configureNavigationBar(_ navigationBar: UINavigationBar?) {
/// do something
}
}
NSObject extension
Now, let’s create a universal extension for NSObject. This extension will contain a method for performing swizzling. This method should have two parameters: one for the default method and the second for the swizzled one. These parameters will be wrapped via the #selector.
Here, we should find the specified methods using selectors and the Objective-C function called class_getInstanceMethod.
Then, we will check if our swizzled method has already been added via class_addMethod. If not, we will call method_exchangeImplementations, which exchanges the implementations of two methods. If the swizzled method has already been added, we will use the class_replaceMethod, which replaces implementing a method for a given class.
extension NSObject {
static func swizzleMethod(
_ originalSelector: Selector,
_ swizzledSelector: Selector
) {
guard
let originalMethod = class_getInstanceMethod(self, originalSelector),
let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
else { return }
let isAdded = class_addMethod(
self,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod)
)
guard isAdded else {
method_exchangeImplementations(originalMethod, swizzledMethod)
return
}
class_replaceMethod(
self,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod)
)
}
}
UIViewController extension
Now, we are ready to create a method for swizzling. We can do it within the UIViewController extension. Let’s create a method called viewWillAppear_swizzle. Here, we will call the NavigationBarManager and our viewWillAppear_swizzle methods.
extension UIViewController {
@objc
private func viewWillAppear_swizzle(_ animated: Bool) {
NavigationBarManager.configureNavigationBar(navigationController?.navigationBar)
viewWillAppear_swizzle(animated)
}
}
It looks like an infinite loop since we call this function in its body. But there won’t be any loop because the viewWillAppear_swizzle selector will point to the viewWillAppear at runtime.
Now, we can create another method to enable swizzling.
extension UIViewController {
static func swizzle() {
swizzleMethod(
#selector(viewWillAppear(_:)),
#selector(viewWillAppear_swizzle(_:))
)
}
}
To perform swizzling, we can call it in the AppDelegate’s didFinishLaunchingWithOptions method.
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
UIViewController.swizzle()
return true
}
}
And that’s it! Now, whenever the viewWillAppear method is called, our viewWillAppear_swizzle method will be performed instead.
Conclusion
We should use this technique with caution. Sometimes, swizzling can relieve headaches. But sometimes, it can cause problems, even if they’re not immediately noticeable.