Swift 中如何实现 Method Swizzling?
在 Swift 中实现 Method Swizzling 比 Objective-C 复杂一些,因为 Swift 有更严格的类型安全和运行时限制。以下是几种实现方式:
1. Objective-C Runtime + @objc
这是最常用的方法,需要方法标记为 @objc:
plaintext
import ObjectiveC.runtime
extension UIViewController {
@objc func swizzled_viewDidLoad() {
// 调用原始实现
self.swizzled_viewDidLoad()
print("viewDidLoad called on \(self)")
}
static let swizzle: Void = {
let originalSelector = #selector(UIViewController.viewDidLoad)
let swizzledSelector = #selector(UIViewController.swizzled_viewDidLoad)
guard let originalMethod = class_getInstanceMethod(self, originalSelector),
let swizzledMethod = class_getInstanceMethod(self, swizzledSelector) else {
return
}
method_exchangeImplementations(originalMethod, swizzledMethod)
}()
}
使用时需要在 application(_:didFinishLaunchingWithOptions:) 中调用:
plaintext
_ = UIViewController.swizzle
2. 使用函数指针(更安全的方式)
plaintext
import ObjectiveC.runtime
private var hasSwizzled = false
extension UIViewController {
static func swizzleViewDidLoad() {
guard !hasSwizzled else { return }
hasSwizzled = true
let originalSelector = #selector(viewDidLoad)
let swizzledSelector = #selector(swizzled_viewDidLoad)
guard let originalMethod = class_getInstanceMethod(self, originalSelector),
let swizzledMethod = class_getInstanceMethod(self, swizzledSelector) else {
return
}
// 添加新方法到类,如果已存在则替换实现
if class_addMethod(self, originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swiddedMethod)) {
// 成功添加新方法后,将旧方法的实现替换为新方法的实现
class_replaceMethod(self, swiddedSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod))
} else {
// 直接交换实现
method_exchangeImplementations(originalMethod, swiddedMethod)
}
}
@objc private func swidded_viewDidLoad() {
// 这里实际上调用的是原始的 viewDidLoad()
self.swidded_viewDidLoad()
print("View Controller loaded: \(type(of: self))")
}
}
3. Swift-specific Method Swizzling(推荐)
对于纯 Swift 类,可以使用函数指针和关联对象:
plaintext
import Foundation
class SwiftyClass {
dynamic func originalFunction() -> String {
return "Original"
}
}
extension SwiftyClass {
private struct AssociatedKeys {
static var customFunctionKey: UInt8 = 0
}
private var customFunction: (() -> String)? {
get { objc_getAssociatedObject(self, &AssociatedKeys.customFunctionKey) as? () -> String }
set { objc_setAssociatedObject(self, &AssociatedKeys.customFunctionKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}
static func enableSwizzling() {
#if canImport(ObjectiveC)
if isSwiftVersionAtLeast(5) && responds(to: Selector(("originalFunction"))) == false{
// Swift runtime doesn't support traditional method swapping well.
// Use function replacement instead.
#else
#endif
// Alternative approach using function pointers:
let instancePtr = UnsafeMutableRawPointer(bitPattern: "instance".hashValue)!
// Store the new implementation in associated object and call it from wrapper function.
}
4. iOS/macOS API Hooking(系统框架)
对于系统框架方法,通常使用更安全的替代方案:
plaintext
// MARK: - Safe Wrapper Pattern (Recommended over direct Swizzling)
protocol ViewControllerLifecycleTracking {}
extension ViewControllerLifecycleTracking where Self: UIViewController {}
private var trackingAssociationKey: UInt8 = 0
extension UIViewController: ViewControllerLifecycleTracking {}
final class LifecycleTracker<T> where T: UIViewController {
static func track(_ viewController:T){
guard !(objc_getAssociatedObject(viewController,&trackingAssociationKey) != nil )else{return}
let tracker=LifecycleTracker()
tracker.setupHooks(for :viewController)
objc_setAssociatedObject(viewController ,&trackingAssociationKey,tracker,.OBJC_ASSOCIATION_RETAIN_NONATOMIC)}
private func setupHooks(for vc:T){print ("Track started for \(vc)");}}
然后在 AppDelegate中启用:
plaintext
func application(_ application:UIApplication,didFinishLaunchingWithOptions launchOptions:[UIApplication.LaunchOptionsKey : Any]?) -> Bool{
_=UIViewController.self; UIViewControllerswizze();return true;}
⚠️ Important Considerations:
- 线程安全: Method Swzzling应该在主线程执行且只执行一次。
- 继承问题: Swzzling会影响所有子类。
- 调试困难: Makes debugging harder due to changed behavior.
- App Store审核: Apple may reject apps that use unsafe runtime manipulation.
🎯 Best Practices:
- Use only when absolutely necessary (logging analytics etc.)
- Always call the original implementation unless you have a very good reason not to do so!
For pure-Swift classes consider alternative patterns like:
- Protocol extensions with default implementations
- Dependency injection
These approaches are safer and more maintainable than traditional Method Swzzling!