Swift中Method Swizzling线程安全的最佳实践与实现建议
Swift中Method Swizzling的线程安全最佳实践
Method Swizzling的核心风险之一就是多线程下并发执行交换操作,或者在交换过程中有线程调用目标方法,导致指针混乱、崩溃或逻辑异常。以下是经过验证的线程安全实践和同步机制:
1. 优先在+load方法中执行Swizzle
系统在加载类时会同步调用+load方法,每个类的+load只会执行一次,且调用时机早于main线程启动,天然避免了多线程并发问题。同时,父类的+load会在子类之前执行,主类的+load会在分类之前执行,能保证方法交换的顺序可控。
示例代码:
extension UIViewController { // 静态常量的初始化闭包只会执行一次,配合+load确保安全 private static let swizzleToken = { let originalSelector = #selector(viewDidAppear(_:)) let swizzledSelector = #selector(swizzled_viewDidAppear(_:)) guard let originalMethod = class_getInstanceMethod(self, originalSelector), let swizzledMethod = class_getInstanceMethod(self, swizzledSelector) else { return } // 先尝试添加原方法的实现(处理分类方法未在主类声明的情况) let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)) if didAddMethod { class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)) } else { method_exchangeImplementations(originalMethod, swizzledMethod) } }() @objc private func swizzled_viewDidAppear(_ animated: Bool) { // 注意:交换后调用swizzled方法等价于调用原实现 swizzled_viewDidAppear(animated) print("ViewDidAppear 被Hook") } override open class func load() { _ = swizzleToken } }
2. 使用Swift静态常量/懒加载保证单次执行
Swift中静态常量的初始化是线程安全且原子性的,即使在多线程环境下访问,初始化闭包也只会执行一次。如果无法使用+load(比如Swift中某些类型没有+load),可以用这种方式替代。
示例:
class CustomLogger { static let shared = CustomLogger() private init() { // 在初始化时执行Swizzle,确保只执行一次 swizzleNetworkMethods() } private func swizzleNetworkMethods() { let originalSelector = #selector(URLSession.dataTask(with:completionHandler:)) let swizzledSelector = #selector(swizzled_dataTask(with:completionHandler:)) guard let originalMethod = class_getInstanceMethod(URLSession.self, originalSelector), let swizzledMethod = class_getInstanceMethod(URLSession.self, swizzledSelector) else { return } method_exchangeImplementations(originalMethod, swizzledMethod) } @objc private func swizzled_dataTask(with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) { swizzled_dataTask(with: url, completionHandler: { data, response, error in print("请求URL:\(url)") completionHandler(data, response, error) }) } }
3. 手动同步:用串行队列或锁保证原子性
如果必须在运行时动态触发Swizzle(比如根据配置决定是否Hook),必须使用同步机制确保同一时间只有一个线程执行交换操作。常用的方案包括:
串行队列+dispatch_sync
private let swizzleSerialQueue = DispatchQueue(label: "com.yourcompany.swizzle.queue") func dynamicSwizzle() { swizzleSerialQueue.sync { let originalSelector = #selector(SomeClass.targetMethod) let swizzledSelector = #selector(SomeClass.swizzled_targetMethod) guard let originalMethod = class_getInstanceMethod(SomeClass.self, originalSelector), let swizzledMethod = class_getInstanceMethod(SomeClass.self, swizzledSelector) else { return } method_exchangeImplementations(originalMethod, swizzledMethod) } }
锁机制(NSLock/os_unfair_lock)
private let swizzleLock = NSLock() func dynamicSwizzle() { swizzleLock.lock() defer { swizzleLock.unlock() } // 执行Swizzle操作 let originalSelector = #selector(SomeClass.targetMethod) let swizzledSelector = #selector(SomeClass.swizzled_targetMethod) guard let originalMethod = class_getInstanceMethod(SomeClass.self, originalSelector), let swizzledMethod = class_getInstanceMethod(SomeClass.self, swizzledSelector) else { return } method_exchangeImplementations(originalMethod, swizzledMethod) }
额外注意事项
- 禁止在实例方法中执行Swizzle:实例方法可能被多个线程多次调用,会导致重复交换,引发不可预测的行为。
- 避免并发调用被Swizzle的方法:即使Swizzle本身是线程安全的,也要确保原方法和Swizzle后的方法在多线程下是安全的(比如原方法不是线程安全的,Swizzle后也需要处理同步)。
- 不要Swizzle私有方法:系统私有方法的实现可能在版本更新中变化,不仅会引发线程安全问题,还可能导致App审核被拒。
内容的提问来源于stack exchange,提问作者shubham mahadik
相关产品推荐
相关产品推荐

