• Swift中的方法交换原理
  • 发布于 2个月前
  • 167 热度
    0 评论
前言
在 iOS 开发中,方法交换(Method Swizzling)是一种极具灵活性的技术,也是面试中经常问到的问题。它允许开发者在运行时修改或替换现有方法的实现。这种技术通常用于在不需要子类化或修改原始类的情况下,增强或改变方法的行为。今天,我们将深入探讨方法交换的原理、用途以及如何在 Swift 中进行方法交换。

什么是方法交换?
方法交换涉及到在运行时交换两个方法的实现。通过这种技术,开发者可以在不更改原始类的情况下,为方法添加或修改行为。

方法交换的用途
方法交换有多种应用场景:
扩展或修改现有方法:在不需要子类化的情况下,对框架或第三方库中的方法进行扩展或修改。
调试或日志记录:例如,你想知道某个方法是否被调用了,但无法修改源码,此时可以交换方法来自定义输出日志,或者来衡量某个方法的性能如何。
实现跨领域关注点:如在多个方法或类中实现认证或缓存功能。

举个具体的例子,Firebase SDK 是一个使用方法交换特别多的 SDK,它通过替换 UIViewController 的生命周期方法,来自动启用视图跟踪,简化了设置过程,几乎不用手动设置,就可以直接开启这个功能。

另一个例子是 Pulse,它可以自动记录 App 内所有的网络请求,原理就是它内部交换了 URLSession 的初始化方法,以便自动捕获通过 URLSession 进行的网络事件。

如何进行方法交换?
传统方法
在以前 OC 时代,我们使用 OC 提供的方法,如 class_getInstanceMethod、class_getClassMethod 和 method_exchangeImplementations,来交换方法的实现。但在 swift 下略微有点不同,我们分别来说,以下在 ViewController.swift 中的代码为例:
import UIKit
class ViewController: UIViewController {
    // 堆代码 duidaima.com
    override func viewDidLoad() {
        super.viewDidLoad()

        let someClass = SomeClass()
        someClass.originalMethod()
    }
}

class SomeClass {
    @objc dynamic func originalMethod() {
        print("I am the original method")
    }
}
运行以上代码,我们会看到 originalMethod 的输出:
I am the original method
现在,我们将添加一些代码以将 originalMethod 的实现替换为 swizzledOriginalMethod 的实现:
import UIKit
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let selector1 = #selector(SomeClass.originalMethod)
        let selector2 = #selector(SomeClass.swizzledOriginalMethod)
        let method1 = class_getInstanceMethod(SomeClass.self, selector1)!
        let method2 = class_getInstanceMethod(SomeClass.self, selector2)!
        method_exchangeImplementations(method1, method2)

        let someClass = SomeClass()
        someClass.originalMethod()
    }
}
class SomeClass {
    @objc dynamic func originalMethod() {
        print("I am the original method")
    }
}
extension SomeClass {
    @objc func swizzledOriginalMethod() {
        print("I am the replacement method")
    }
}
运行以上代码,我们可以确认在调用 originalMethod 时,实际上调用的是 swizzledOriginalMethod:
I am the replacement method
现代 swift 方法
在 swift 中有个更简单的方法,Swift 中添加了 @_dynamicReplacement 注解,以支持更原生的方法交换实现。在看下以下在 ViewController.swift 中的代码:
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let someClass = SomeClass()
        someClass.originalMethod()
        someClass.orignalMethodWithParameters(for: "Param1", param2: "Param2")
        print(someClass.originalMethodWithParametersAndReturnType(param: "Param"))
    }
}

class SomeClass {
    dynamic func originalMethod() {
        print("I am the original method")
    }

    dynamic func orignalMethodWithParameters(for param1: String, param2: String) {
        print("I am the original method with parameters - param1: \(param1) - param2: \(param2)")
    }

    dynamic func originalMethodWithParametersAndReturnType(param: String) -> Bool {
        print("I am the original method with parameter - param: \(param) - and return type: Bool")
        return true
    }
}
当运行上述代码时,我们将看到以下输出:
I am the original method
I am the original method with parameters - param1: Param1 - param2: Param2
I am the original method with parameter - param: Param - and return type: Bool
true
现在,添加以下代码以 @_dynamicReplacement 注解来用新实现替换原始方法:
extension SomeClass {
    @_dynamicReplacement(for: originalMethod)
    func replacementMethod() {
        print("I'm the replacement method")
    }

    @_dynamicReplacement(for: orignalMethodWithParameters(for:param2:))
    func replacementMethodWithParameters(param1: String, param2: String) {
        print("I am the replacement method with parameters - param1: \(param1) - param2: \(param2)")
    }

    @_dynamicReplacement(for: originalMethodWithParametersAndReturnType(param:))
    func replacement_withParameters_andReturnType(param: String) -> Bool {
        print("I am the replacement method with parameter - param: \(param) - and return type: Bool")
        return false
    }
}
再次运行应用程序,我们将在控制台中看到以下输出:
I'm the replacement method
I am the replacement method with parameters - param1: Param1 - param2: Param2
I am the replacement method with parameter - param: Param - and return type: Bool
false
如上所示,扩展中的方法实现已经替换了 SomeClass 的原始方法实现,而无需我们对子类进行子类化。

优缺点
像任何技术解决方案一样,在考虑方法交换时,权衡其优缺点是至关重要的。它应该作为最后的手段,当其他方法都不可行或成本很高时使用。
1.方法交换非常强大,但如果使用不当,也可能带来风险。它修改了现有方法的行为,这可能导致意外的副作用或与代码库其他部分的冲突。
2.应谨慎使用交换,并且仅在必要时使用。重要的是要彻底测试并确保交换不会引入错误或性能问题。
3.交换可能使代码库更难以理解和维护,因为方法的行为可能并不立即显而易见。
4.如果使用方法交换,重要的是要遵循最佳实践:
  .仅在自己的类或类别中进行方法交换,以避免与其他代码发生冲突。
  .使用唯一的方法名称以防止命名冲突。
  .确保在交换方法时线程安全。

  .清晰地记录和注释交换的方法。


替代方案
如果可能的话,你应该考虑以下替代方案来替代交换,因为相对来说以下方案更安全,更易于维护:
子类化:使用集成子类化和重写方法通常是一种更安全和更易于维护的方法。
组合:使用组合和包装对象可以提供类似的好处,而无需修改原始类。
依赖注入:通过协议或委托注入行为,可以在不进行运行时修改的情况下实现灵活性。

总之,方法交换是一种强大的技术工具,但也需要谨慎使用。相信读完本文能帮助你更好地理解和运用这项技术,也能在面试中应对自如。
用户评论