Method swizzling in iOS swift

What is method swizzling?

Method swizzling 是在运行时更改现有选择器的实现的过程。简单地说,我们可以在运行时更改方法的功能。

Note: 这是一个Objective-C运行时特性。

例如:如果你想跟踪添加到UserDefaults中的键和值,并在所有键之前添加一个前缀字符串,你可以用你自己的方法切换setValue:forKey方法的实现。因此,所有对setValue:forKey方法的调用都将被路由到新的选择器方法。您可以创建添加了前缀的新键字符串,并使用新键调用setValue:forKey方法的原始实现。你会困惑它是否会以无限循环结束。但事实并非如此。(注意:你可以通过在Objc或swift扩展中使用category来做同样的事情。只需创建一个新方法,并从这个新方法调用超级setValue:forKey方法。但是,如果导入的第三方库和框架直接使用setValue:forKey方法怎么办? 这些库不知道新的自定义方法,也不知道我们需要向键添加前缀。这就是method swizzling发挥作用的时候了). 我把这个例子从以下文章👇🏻👇🏻👇🏻👇🏻👇🏻👇🏻。

在继续之前请阅读Michael Mavris的文章。看看stackoverflow上关于如何在swift 3和4中做method swizzling的帖子。

How to do method swizzling in swift 4?

Well, its easy:

  • Create a new method with your custom implementation for a method that you want to swizzle.
  • Get the class representation.
  • Get the old method selector reference.
  • Get the new method selector reference.
  • Ask the objective-C runtime to switch the selectors.
  • Take a deep breath and relax.!! 😂.

Let’s swizzle!!

让我们为UIColor类的description方法创建一个交换方法。

如果你尝试打印一个UIColor对象,它会像这样打印RGBA值:

print(UIColor.red) // prints UIExtendedSRGBColorSpace 1 0 0 1

我们可以打印颜色,因为UIColor有一个描述方法,它将返回颜色的字符串表示。让我们试试这种方法。

import Foundation
import UIKit
public extension UIColor {
    @objc func colorDescription() -> String {
        return "Printing rainbow colours."
    }
    private static let swizzleDesriptionImplementation: Void = {
        let instance: UIColor = UIColor.red
        let aClass: AnyClass! = object_getClass(instance)
        let originalMethod = class_getInstanceMethod(aClass, #selector(description))
        let swizzledMethod = class_getInstanceMethod(aClass, #selector(colorDescription))
        if let originalMethod = originalMethod, let swizzledMethod = swizzledMethod {
        // switch implementation..
        method_exchangeImplementations(originalMethod, swizzledMethod)
        }
    }()
    public static func swizzleDesription() {
        _ = self.swizzleDesriptionImplementation
    }
}

我认为上面的代码很容易解释。在我的视图控制器中,我添加了以下代码。

override func viewDidLoad() {
    super.viewDidLoad()
    print(UIColor.red)
    print(UIColor.green)
    UIColor.swizzleDesription()
    print(“\nswizzled\n”)
    print(UIColor.red)
    print(UIColor.red)
    UIColor.swizzleDesription()
    print(“\nTrying to swizzle again\n”)
    print(UIColor.red)
    print(UIColor.red)
}

Output

UIExtendedSRGBColorSpace 1 0 0 1
UIExtendedSRGBColorSpace 0 1 0 1
swizzled
Printing rainbow colours.
Printing rainbow colours.
Trying to swizzle again
Printing rainbow colours.
Printing rainbow colours.

Swift的静态成员是隐式惰性(lazy)的。这就是为什么swizzledescriptionimplementation没有被再次调用,也没有再次发生swizzledescriptionimplementation的原因。 将修改方法的所有操作封闭在计算的全局常量的惰性初始化块中,可以确保该过程只执行一次(因为这些变量或常量的初始化在后台使用dispatch_once)。

Drawbacks of method swizzling

  • 如果您正在使用任何标准类方法进行交换,那么最好确保交换成功,并调用新的交换方法。 如果你使用的框架(例如Firebase)使用了交换,请确保你没有混合他们已经交换的一些方法。如果交换发生多次,要么您的代码不能工作,要么firebase(或任何其他框架混合相同的方法)不能工作。

  • 当更新的iOS版本发布时,交换有可能失败。你可能每次都要反复核对。

  • 如果你发行的框架(例如一些分析框架)被数百款应用所使用,那么最好不要在这种情况下使用交换功能。如果你想使用交换,请确保应用程序开发人员知道框架所做的交换。确保将其添加到文档中。

  • 在子类中进行混合将是一个头痛的事。可能会发生意想不到的事情。