Swift中Method Swizzling未调用UIViewController原touchesBegan/Ended方法
问题:Swift中Method Swizzling拦截UIResponder触摸方法后,UIViewController原方法未触发
尝试通过Method Swizzling拦截所有UIResponder实例的touchesBegan和touchesEnded方法,但发现UIViewController中重写的原始触摸方法没有被调用。使用的代码如下:
extension UIResponder { static let swizzleTouchMethods: Void = { let originalBeganSelector = #selector(touchesBegan(_:with:)) let swizzledBeganSelector = #selector(xxx_touchesBegan(_:with:)) guard let originalBeganMethod = class_getInstanceMethod(UIView.self, originalBeganSelector), let swizzledBeganMethod = class_getInstanceMethod(UIView.self, swizzledBeganSelector) else { return } method_exchangeImplementations(originalBeganMethod, swizzledBeganMethod) let originalMovedSelector = #selector(touchesMoved(_:with:)) let swizzledMovedSelector = #selector(xxx_touchesMoved(_:with:)) guard let originalMovedMethod = class_getInstanceMethod(UIView.self, originalMovedSelector), let swizzledMovedMethod = class_getInstanceMethod(UIView.self, swizzledMovedSelector) else { return } method_exchangeImplementations(originalMovedMethod, swizzledMovedMethod) let originalEndedSelector = #selector(touchesEnded(_:with:)) let swizzledEndedSelector = #selector(xxx_touchesEnded(_:with:)) guard let originalEndedMethod = class_getInstanceMethod(UIView.self, originalEndedSelector), let swizzledEndedMethod = class_getInstanceMethod(UIView.self, swizzledEndedSelector) else { return } method_exchangeImplementations(originalEndedMethod, swizzledEndedMethod) }() @objc func xxx_touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { if let touch = touches.first { print("Swizzled touch began at: radius \(touch.majorRadius)") } // Call the original method (which is now swizzled) self.xxx_touchesBegan(touches, with: event) } @objc func xxx_touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { if let touch = touches.first { print("Swizzled touch moved at: radius \(touch.majorRadius)") } // Call the original method (which is now swizzled) self.xxx_touchesMoved(touches, with: event) } @objc func xxx_touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { if let touch = touches.first { print("Swizzled touch ended at: radius \(touch.majorRadius)") } // Call the original method (which is now swizzled) self.xxx_touchesEnded(touches, with: event) } } final class MyViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() UIResponder.swizzleTouchMethods } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesBegan(touches, with: event) print("original touchesBegan event") } override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesEnded(touches, with: event) print("original touchesEnded event") } }
问题原因
- 错误的Swizzle目标类:当前代码交换的是
UIView类的触摸方法,而UIViewController是UIResponder的直接子类,它自身重写了touchesBegan/touchesEnded方法,事件响应链中会优先调用VC的这些方法,但你的Swizzle操作并未影响UIViewController类的方法实现。 - Swizzle逻辑不完整:未处理子类未重写父类方法的情况,直接交换可能导致部分场景失效。
解决方案
修正后的代码
import UIKit extension UIResponder { static let swizzleTouchMethods: Void = { // 针对UIResponder类进行方法交换,覆盖所有子类 guard let cls = UIResponder.self as? AnyClass else { return } // 处理touchesBegan交换 let originalBeganSelector = #selector(touchesBegan(_:with:)) let swizzledBeganSelector = #selector(swizzled_touchesBegan(_:with:)) guard let originalMethod = class_getInstanceMethod(cls, originalBeganSelector), let swizzledMethod = class_getInstanceMethod(cls, swizzledBeganSelector) else { return } // 先尝试添加原方法实现,避免子类未重写时交换失效 let didAddBeganMethod = class_addMethod(cls, originalBeganSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)) if didAddBeganMethod { class_replaceMethod(cls, swizzledBeganSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)) } else { method_exchangeImplementations(originalMethod, swizzledMethod) } // 处理touchesEnded交换 let originalEndedSelector = #selector(touchesEnded(_:with:)) let swizzledEndedSelector = #selector(swizzled_touchesEnded(_:with:)) guard let originalEndedMethod = class_getInstanceMethod(cls, originalEndedSelector), let swizzledEndedMethod = class_getInstanceMethod(cls, swizzledEndedSelector) else { return } let didAddEndedMethod = class_addMethod(cls, originalEndedSelector, method_getImplementation(swizzledEndedMethod), method_getTypeEncoding(swizzledEndedMethod)) if didAddEndedMethod { class_replaceMethod(cls, swizzledEndedSelector, method_getImplementation(originalEndedMethod), method_getTypeEncoding(originalEndedMethod)) } else { method_exchangeImplementations(originalEndedMethod, swizzledEndedMethod) } // 处理touchesMoved交换 let originalMovedSelector = #selector(touchesMoved(_:with:)) let swizzledMovedSelector = #selector(swizzled_touchesMoved(_:with:)) guard let originalMovedMethod = class_getInstanceMethod(cls, originalMovedSelector), let swizzledMovedMethod = class_getInstanceMethod(cls, swizzledMovedSelector) else { return } let didAddMovedMethod = class_addMethod(cls, originalMovedSelector, method_getImplementation(swizzledMovedMethod), method_getTypeEncoding(swizzledMovedMethod)) if didAddMovedMethod { class_replaceMethod(cls, swizzledMovedSelector, method_getImplementation(originalMovedMethod), method_getTypeEncoding(originalMovedMethod)) } else { method_exchangeImplementations(originalMovedMethod, swizzledMovedMethod) } }() @objc private func swizzled_touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { if let touch = touches.first { print("Swizzled touch began at: radius \(touch.majorRadius)") } // 交换后,调用swizzled方法等价于调用原方法 self.swizzled_touchesBegan(touches, with: event) } @objc private func swizzled_touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { if let touch = touches.first { print("Swizzled touch ended at: radius \(touch.majorRadius)") } self.swizzled_touchesEnded(touches, with: event) } @objc private func swizzled_touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { if let touch = touches.first { print("Swizzled touch moved at: radius \(touch.majorRadius)") } self.swizzled_touchesMoved(touches, with: event) } } final class MyViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // 建议将Swizzle放在App启动时执行一次,比如AppDelegate中 UIResponder.swizzleTouchMethods } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesBegan(touches, with: event) print("original touchesBegan event") } override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesEnded(touches, with: event) print("original touchesEnded event") } }
关键修正点
- 针对UIResponder类交换:确保所有UIResponder子类(包括UIViewController、UIView等)的触摸方法都会被拦截。
- 完善方法交换逻辑:通过
class_addMethod处理子类未重写父类方法的场景,避免交换失效。 - 优化Swizzle时机:建议在App启动时(如
AppDelegate.application(_:didFinishLaunchingWithOptions:))执行一次,避免重复交换。
内容的提问来源于stack exchange,提问作者Sajal Gupta
相关产品推荐
相关产品推荐

