📜 ⬆️ ⬇️

Method Swizzling and Swift: but there is a nuance

Sometimes for convenience, sometimes in order to get around a bug in the framework, and sometimes just because of despair, you may need to override the behavior of some method of a class created by someone else. Method Swizzling allows you to replace your method right at runtime, while leaving the original implementation available.

The Objective-C Runtime article . Theory and practical application of this process is well described. But with the transition to Swift some nuances appear.

Let's start with an example. For example, in the third-party framework Charts , there is some class ChartRenderer , in which there is a method drawDots , which on the chart draws a circle in the nodal points. And we really want not a circle, but an asterisk. And we don’t really want to climb inside the framework either, because from time to time its updates come out and losing them is a pity.

Therefore, we will create an extension of the ChartRenderer class, which will replace the drawDots method with our drawStars method right at runtime. No need to worry as the other objects in the Charts call the ChartRenderer , each time the drawStars method call is answered by the drawStars method.
')
extension ChartRenderer { public override class func initialize() { struct Static { static var token: dispatch_once_t = 0 } // ,     if self !== UIViewController.self { return } dispatch_once(&Static.token) { let originalSelector = Selector("drawDots:") let swizzledSelector = Selector("drawStars:") let originalMethod = class_getInstanceMethod(self, originalSelector) let swizzledMethod = class_getInstanceMethod(self, swizzledSelector) let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)) if didAddMethod { class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)) } else { method_exchangeImplementations(originalMethod, swizzledMethod) } } } //  func drawStars(color: CGColor) { //  // ,     drawStars(...), //    drawDots(...),    "drawStars:" } } 

In all materials on Objective-C they write that methods need to be substituted in load () , and not in initialize () , because the first is called only once for the class, and the second is also called for all subclasses. But since runtime, when using Swift, does not call load () at all, we have to make sure that this initialize () is called by the class, and not by any of the heirs, and that the replacement will be performed only once using dispatch_once .

It is worth noting that there is an alternative option, which is simpler but less visual: instead of an extension, perform all these operations in the AppDelegate application (_: didFinishLaunchingWithOptions :) .

If the ChartRenderer is written in Objective-C, everything should work this way. Good article is on NSHipster ( English ).

And now about the nuance, because of which you can break your head. When using method swizzling in the case when both the project and the class with the replaceable method are written in Swift, this one must meet two requirements:


Like that:

 class ChartRenderer: NSObject { dynamic func drawDots() { //  } } 

The nature of this nuance is explained in the article Using Swift with Cocoa and Objective-C :
Requiring Dynamic Dispatch

It is not a problem for the Objective-C run-time API, it doesn’t guarantee the dispatch of a property, method, subscript, or initializer. The Swift compiler may still be devirtualize or inline a member of the code, bypassing the Objective-C runtime. Dynamically dispatched. Because of the fact that they are dispatched using the Objective-C runtime, they are marked with the objc attribute.
Requiring dynamic dispatch is rarely necessary. However, you must replace the API. For example, you can use the method while the app is running. It’s not a problem.


In addition to the above articles, the English problem is well described in a certain Bartek Hugo here .

Source: https://habr.com/ru/post/274545/


All Articles