SwiftUI 利用 Swizz 黑魔法为系统创建的默认对象插入新协议方法(一)

SwiftUI 利用 Swizz 黑魔法为系统创建的默认对象插入新协议方法(一)
最新回答
贴心小棉袄

2026-06-11 12:57:14

在 SwiftUI 中,可通过 Method Swizzling 修改系统默认对象的底层行为,为其插入自定义协议方法以实现拖放操作等功能的扩展。 以下是具体实现思路与关键步骤:

1. 理解 SwiftUI 与 UIKit 的底层关系
  • SwiftUI 是声明式框架,但底层仍依赖 UIKit(如 UIView、UIViewController)。当 SwiftUI 未暴露某些功能(如拖放取消时的回调)时,可通过直接操作 UIKit 对象实现。
  • 示例场景:拖放操作取消时恢复界面状态。SwiftUI 原生未提供相关回调,需通过 Swizzling 拦截 UIKit 的拖放事件处理逻辑。
2. Method Swizzling 的核心原理
  • 定义:动态交换两个方法的实现(如 methodA 和 methodB),使调用 methodA 时实际执行 methodB 的逻辑,反之亦然。
  • 关键步骤:

    获取目标类的 Method 类型指针(通过 class_getInstanceMethod)。

    使用 method_exchangeImplementations 交换方法实现。

    确保在运行时(如 +load 方法或 DispatchQueue.once)执行 Swizzling,避免重复操作。

3. 为系统对象插入自定义协议方法
  • 目标:在 UIKit 的拖放相关类(如 UIDropInteraction 或 UIView 的子类)中插入方法,监听拖放状态变化。
  • 实现步骤:

    创建协议与扩展:

    protocol DragDropDelegate: AnyObject { func didCancelDrag()}extension UIView { private struct AssociatedKeys { static var delegateKey = "dragDropDelegateKey" } weak var dragDropDelegate: DragDropDelegate? { get { return objc_getAssociatedObject(self, &AssociatedKeys.delegateKey) as? DragDropDelegate } set { objc_setAssociatedObject(self, &AssociatedKeys.delegateKey, newValue, .OBJC_ASSOCIATION_ASSIGN) } }}

    通过关联对象(Associated Objects)为 UIView 添加自定义属性 dragDropDelegate,避免修改系统类。

    Swizzling 拖放相关方法:

    extension UIView { static func swizzleDragDropMethods() { let originalSelector = #selector(UIView.endTracking(_:with:)) let swizzledSelector = #selector(UIView.swizzledEndTracking(_:with:)) guard let originalMethod = class_getInstanceMethod(UIView.self, originalSelector), let swizzledMethod = class_getInstanceMethod(UIView.self, swizzledSelector) else { return } method_exchangeImplementations(originalMethod, swizzledMethod) } @objc func swizzledEndTracking(_ touch: UITouch?, with event: UIEvent?) { swizzledEndTracking(touch, with: event) // 调用原方法 if let delegate = dragDropDelegate { delegate.didCancelDrag() // 插入自定义逻辑 } }}

    交换 endTracking(_:with:) 方法(触摸结束时调用),在原有逻辑后触发 didCancelDrag 回调。

    通过 @objc 标记方法以确保动态性。

    调用 Swizzling:

    // 在 App 启动时(如 AppDelegate 的 didFinishLaunching)调用:UIView.swizzleDragDropMethods()
4. 在 SwiftUI 中集成
  • 通过 UIViewRepresentable 包装自定义 UIView,并设置 DragDropDelegate:struct CustomDragView: UIViewRepresentable { class Coordinator: NSObject, DragDropDelegate { func didCancelDrag() { // 恢复界面状态的逻辑 } } func makeCoordinator() -> Coordinator { Coordinator() } func makeUIView(context: Context) -> UIView { let view = UIView() view.dragDropDelegate = context.coordinator return view } func updateUIView(_ uiView: UIView, context: Context) {}}
  • 在 SwiftUI 中使用 CustomDragView,即可在拖放取消时触发恢复逻辑。
5. 注意事项
  • 安全性:Swizzling 会修改系统行为,可能导致不可预测的副作用(如与其他库冲突)。建议仅在调试或必要场景使用,并充分测试。
  • 兼容性:不同 iOS 版本中 UIKit 的内部实现可能不同,需验证 Swizzling 的稳定性。
  • 替代方案:优先使用 SwiftUI 原生功能(如 onDrag、onDrop 修饰符),或通过 UIViewRepresentable 直接调用 UIKit API 而非 Swizzling。
6. 完整代码示例// 1. 定义协议与扩展protocol DragDropDelegate: AnyObject { func didCancelDrag()}extension UIView { private struct AssociatedKeys { static var delegateKey = "dragDropDelegateKey" } weak var dragDropDelegate: DragDropDelegate? { get { objc_getAssociatedObject(self, &AssociatedKeys.delegateKey) as? DragDropDelegate } set { objc_setAssociatedObject(self, &AssociatedKeys.delegateKey, newValue, .OBJC_ASSOCIATION_ASSIGN) } } static func swizzleDragDropMethods() { let originalSelector = #selector(UIView.endTracking(_:with:)) let swizzledSelector = #selector(UIView.swizzledEndTracking(_:with:)) guard let originalMethod = class_getInstanceMethod(UIView.self, originalSelector), let swizzledMethod = class_getInstanceMethod(UIView.self, swizzledSelector) else { return } method_exchangeImplementations(originalMethod, swizzledMethod) } @objc func swizzledEndTracking(_ touch: UITouch?, with event: UIEvent?) { swizzledEndTracking(touch, with: event) dragDropDelegate?.didCancelDrag() }}// 2. 调用 Swizzling(在 App 启动时)class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { UIView.swizzleDragDropMethods() return true }}// 3. SwiftUI 集成struct CustomDragView: UIViewRepresentable { class Coordinator: NSObject, DragDropDelegate { func didCancelDrag() { print("Drag cancelled! Restore UI state here.") } } func makeCoordinator() -> Coordinator { Coordinator() } func makeUIView(context: Context) -> UIView { let view = UIView() view.dragDropDelegate = context.coordinator return view } func updateUIView(_ uiView: UIView, context: Context) {}}

通过上述方法,可在 SwiftUI 中利用 Swizzling 为系统对象插入自定义逻辑,实现原生框架未提供的功能(如拖放取消回调)。但需谨慎评估风险,优先探索官方支持的替代方案。