这里每天分享一个 iOS 的新知识,快来关注我吧
前言
=====
在 iOS 开发中,方法交换(Method Swizzling)是一种极具灵活性的技术,也是面试中经常问到的问题。它允许开发者在运行时修改或替换现有方法的实现。这种技术通常用于在不需要子类化或修改原始类的情况下,增强或改变方法的行为。
今天,我们将深入探讨方法交换的原理、用途以及如何在 Swift 中进行方法交换。
什么是方法交换?
方法交换涉及到在运行时交换两个方法的实现。通过这种技术,开发者可以在不更改原始类的情况下,为方法添加或修改行为。
方法交换的用途
方法交换有多种应用场景:
-
扩展或修改现有方法:在不需要子类化的情况下,对框架或第三方库中的方法进行扩展或修改。
-
调试或日志记录:例如,你想知道某个方法是否被调用了,但无法修改源码,此时可以交换方法来自定义输出日志,或者来衡量某个方法的性能如何。
-
实现跨领域关注点:如在多个方法或类中实现认证或缓存功能。
举个具体的例子,Firebase SDK 是一个使用方法交换特别多的 SDK,它通过替换 UIViewController
的生命周期方法,来自动启用视图跟踪,简化了设置过程,几乎不用手动设置,就可以直接开启这个功能。
另一个例子是 Pulse,它可以自动记录 App 内所有的网络请求,原理就是它内部交换了 URLSession
的初始化方法,以便自动捕获通过 URLSession
进行的网络事件。
如何进行方法交换?
传统方法
在以前 OC 时代,我们使用 OC 提供的方法,如 class_getInstanceMethod
、class_getClassMethod
和 method_exchangeImplementations
,来交换方法的实现。
但在 swift 下略微有点不同,我们分别来说,以下在 ViewController.swift
中的代码为例:
swift
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let someClass = SomeClass()
someClass.originalMethod()
}
}
class SomeClass {
@objc dynamic func originalMethod() {
print("I am the original method")
}
}
运行以上代码,我们会看到 originalMethod
的输出:
css
I am the original method
现在,我们将添加一些代码以将 originalMethod
的实现替换为 swizzledOriginalMethod
的实现:
swift
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
:
css
I am the replacement method
现代 swift 方法
在 swift 中有个更简单的方法,Swift 中添加了 @_dynamicReplacement
注解,以支持更原生的方法交换实现。
在看下以下在 ViewController.swift
中的代码:
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
}
}
当运行上述代码时,我们将看到以下输出:
sql
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
注解来用新实现替换原始方法:
swift
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
}
}
再次运行应用程序,我们将在控制台中看到以下输出:
vbnet
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
的原始方法实现,而无需我们对子类进行子类化。
优缺点
像任何技术解决方案一样,在考虑方法交换时,权衡其优缺点是至关重要的。它应该作为最后的手段,当其他方法都不可行或成本很高时使用。
-
方法交换非常强大,但如果使用不当,也可能带来风险。它修改了现有方法的行为,这可能导致意外的副作用或与代码库其他部分的冲突。
-
应谨慎使用交换,并且仅在必要时使用。重要的是要彻底测试并确保交换不会引入错误或性能问题。
-
交换可能使代码库更难以理解和维护,因为方法的行为可能并不立即显而易见。
-
如果使用方法交换,重要的是要遵循最佳实践:
-
仅在自己的类或类别中进行方法交换,以避免与其他代码发生冲突。
-
使用唯一的方法名称以防止命名冲突。
-
确保在交换方法时线程安全。
-
清晰地记录和注释交换的方法。
替代方案
如果可能的话,你应该考虑以下替代方案来替代交换,因为相对来说以下方案更安全,更易于维护:
-
子类化:使用集成子类化和重写方法通常是一种更安全和更易于维护的方法。
-
组合:使用组合和包装对象可以提供类似的好处,而无需修改原始类。
-
依赖注入:通过协议或委托注入行为,可以在不进行运行时修改的情况下实现灵活性。
总之,方法交换是一种强大的技术工具,但也需要谨慎使用。相信读完本文能帮助你更好地理解和运用这项技术,也能在面试中应对自如。
这里每天分享一个 iOS 的新知识,快来关注我吧
本文同步自微信公众号 "iOS新知",每天准时分享一个新知识,这里只是同步,想要及时学到就来关注我吧!