基于本文回答

播面 播面

刷题像听歌,多听自然懂
0
评论

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:

  1. 线程安全: Method Swzzling应该在主线程执行且只执行一次。
  2. 继承问题: Swzzling会影响所有子类。
  3. 调试困难: Makes debugging harder due to changed behavior.
  4. 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!

00:00
00:00