Practical scenarios for using Objective-C method dispatch system in Swift.
While working with swift we can witness a difference in the nature of this language from its predecessor especially in the method dispatch system. Objective-C is very dynamic in nature and it makes it very easy to work with but at a cost of stability and performance.
As we know that the main magic in Objective-C comes from its dynamic message based dispatch system, whereas Swift relies on V or Witness table based dispatch system.
These are the some of the cases where one can refer to the Objective-C run time to exploit this dynamism. Obviously they are not considered to be a good practice and can also create performance issues.
- Adding stored properties in extension:
While working with some native types its easier to add stored properties in extension without subclassing the actual type.
fileprivate var storedObjectKey: UInt8 = 0
extension UIViewController {
var storedProperty: String? {
get {
guard let storedValue = objc_getAssociatedObject(self, &storedObjectKey) as? String else {
return nil
}
return storedValue
}
set {
objc_setAssociatedObject(self, &storedObjectKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
In the above example, a stored property is being created on UIViewController in the extension which is not possible in pure Swift environment.
2. Method Swizzling
Swizzling means to switch the implementation of a method at run time. This concept is also dependent on Objective-C run time. I have seen some third party libraries also using it if they need to add an hook in some App life cycle method.
extension UIViewController {
static let modalDismissed = Notification.Name("modalDismissed")
static func swizzleDismiss() {
let originalSelector = #selector(UIViewController.dismiss(animated:completion:))
let swizzledSelector = #selector(UIViewController.trackedDismiss(animated:completion:))
let originalMethod = class_getInstanceMethod(self, originalSelector)
let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
if let originalMethod = originalMethod
, let swizzledMethod = swizzledMethod {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
@objc func trackedDismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
trackedDismiss(animated: flag) {
completion?()
NotificationCenter.default.post(name: UIViewController.modalDismissed, object: nil)
}
}
}
In the above example a dismiss() method is being swizzled with the local method. Now every time this method gets called our native method will be called and which is throwing a notification which can be caught in other places.
3. Overriding a computed property which is defined in super class extension
In case we add a property in an extension of a super class and try to override it in a subclass, it wont let the sub class override property unless we use the “objc” directive with the super class.
extension UIViewController {
@objc var someProperty: String {
return "From UIViewController"
}
}
class CustomVC: UIViewController {
override var someProperty: String {
"From CustomVC"
}
}
let customVC = CustomVC()
print("\(customVC.someProperty)")
In the above example a computed property is used to provide a default value for all UIViewControllers and for specific cases its can be overridden.