Using Method Swizzling in Swift
Let’s see what is it and how it works
Method Swizzling in iOS Development is a runtime feature used to change the implementation of an existing method at runtime. Prior to Swift 5.1, we could change the implementation of Objective-C methods marked with the @objc attribute. In Swift 5.1, Apple added an alternative feature to Swift to change the implementation of Swift methods at runtime.
Why is it needed?
Let’s imagine that you have a third-party library in your project that contains a method in some class, and the implementation of this method does not work correctly or not the way you want and you want to change the implementation of this method. You can do this using the Method Swizzling feature.
When is this even useful?
Let’s imagine you are writing applications for iOS/iPadOS and at some point you want to change the language of the application on the fly (at runtime) without reloading the application. At this time, applying Method Swizzling would be useful.
How it works?
Method swizzling in Swift is a powerful technique that allows developers to modify the behavior of existing methods at runtime. It enables us to intercept calls to methods and replace them with our custom implementations. This technique has proven to be immensely useful in many scenarios, such as adding logging, measuring performance, or extending functionality of existing classes without modifying their original source code. To understand how method swizzling works, let’s delve into the core concepts of this technique. Method swizzling is essentially an approach to exchange the implementation of two methods in a class. In Swift, this can be achieved by leveraging the Objective-C runtime library, which is fully compatible with Swift. To perform method swizzling, we need to perform the following steps:
- Identify the Class and the Methods: First, we need to identify the class whose methods we want to swizzle. We also need to identify the two methods that we want to exchange their implementations.
- Get Method Pointers: Once we have identified the class and methods, we need to retrieve their implementation pointers. This can be done using the `class_getInstanceMethod` function from the Objective-C runtime library. We can pass the class and the method name as parameters to obtain the pointer to the method’s implementation.
- Create New Methods: After obtaining the pointers to the original methods, we need to create replacements for them. These new methods will have the desired behavior that we want to inject into the class. We can write our custom implementation as separate functions or methods.
- Add New Implementations to Class: Now, we have the original methods and our custom methods ready. To perform the swizzling, we need to associate the original implementations with our custom methods and associate the custom implementations with the original methods. We can use the `method_exchangeImplementations` function from the Objective-C runtime library to achieve this.
- Verify Swizzling: Finally, we need to verify that the swizzling was successful by invoking the original methods. We should see that the behavior has been modified as per our custom implementations. It’s important to note that method swizzling should be used judiciously. Modifying the behavior of existing methods can lead to unexpected consequences and introduce hard-to-debug issues if not done carefully. It’s recommended to thoroughly test the swizzling code and ensure that it doesn’t interfere with the existing functionality of the classes. Let’s illustrate method swizzling with a simple example. Consider a scenario where we want to add logging to the viewWillAppear method of a view controller. We can accomplish this by swizzling the method:
extension UIViewController {
@objc func logging_viewWillAppear(_ animated: Bool) {
print("View will appear: \(self)")
self.logging_viewWillAppear(animated)
}
static func swizzleViewWillAppear() {
let originalSelector = #selector(viewWillAppear(_:))
let swizzledSelector = #selector(logging_viewWillAppear(_:))
guard let originalMethod = class_getInstanceMethod(self, originalSelector),
let swizzledMethod = class_getInstanceMethod(self, swizzledSelector) else {
return
}
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
In this example, we extend the `UIViewController` class and define a new method called `logging_viewWillAppear`, which includes our logging logic and invokes the original `viewWillAppear` method using the `self` reference. Then, we use the `class_getInstanceMethod` and `method_exchangeImplementations` functions to swizzle the original `viewWillAppear` method with our custom implementation. To enable the swizzling, we just need to call the `swizzleViewWillAppear` method at an appropriate point, such as during app launch or in the `applicationDidFinishLaunching` method.
func applicationDidFinishLaunching() {
UIViewController.swizzleViewWillAppear()
}
With this swizzling in place, every time the `viewWillAppear` method is called on a `UIViewController` instance, our custom implementation will be invoked, printing a log statement before the actual `viewWillAppear` code executes. In conclusion, method swizzling in Swift is a powerful technique that allows developers to modify the behavior of existing methods at runtime. By leveraging the Objective-C runtime library, we can exchange the implementations of two methods in a class, thereby injecting custom logic into existing classes without modifying their original source code. However, method swizzling should be used with caution and thoroughly tested to avoid unintended consequences.