自定义UIView的Combine扩展tabPublisher无法工作,求排查问题
问题:自定义UIView的tabPublisher手势识别器无法生效
我不清楚为什么自定义的UIView扩展中的tabPublisher无法正常工作。我查阅了Swift CombineCocoa库,想要实现UIView的Combine扩展(CombineCocoa并未提供UIView相关的扩展)。我参考了其他自定义UIView扩展的示例,但自己实现的代码没有效果。
我已参考CombineCocoa的CombineControlTarget.swift实现了以下代码:
extension Combine.Publishers { struct CustomTarget<Control: AnyObject>: Publisher { // swiftlint: disable nesting typealias Output = Void typealias Failure = Never // swiftlint: enable nesting let control: Control let addTargetAction: (Control, AnyObject, Selector) -> Void let removeTargetAction: (Control, AnyObject, Selector) -> Void init( control: Control, addTargetAction: @escaping (Control, AnyObject, Selector) -> Void, removeTargetAction: @escaping (Control, AnyObject, Selector) -> Void) { self.control = control self.addTargetAction = addTargetAction self.removeTargetAction = removeTargetAction } func receive<S>(subscriber: S) where S: Subscriber, S.Failure == Failure, S.Input == Output { let subscription = Subscription( subscriber: subscriber, control: control, addTargetAction: addTargetAction, removeTargetAction: removeTargetAction) subscriber.receive(subscription: subscription) } } } extension Combine.Publishers.CustomTarget { class Subscription<S: Subscriber, Control: AnyObject>: Combine.Subscription where S.Input == Void { private var subscriber: S? weak private var control: Control? let removeTargetAction: (Control, AnyObject, Selector) -> Void let action = #selector(handleAction) init( subscriber: S, control: Control, addTargetAction: @escaping (Control, AnyObject, Selector) -> Void, removeTargetAction: @escaping (Control, AnyObject, Selector) -> Void) { self.subscriber = subscriber self.control = control self.removeTargetAction = removeTargetAction addTargetAction(control, self, action) } func request(_ demand: Subscribers.Demand) { } func cancel() { subscriber = nil removeTargetAction(control!, self, action) } @objc func handleAction() { _ = subscriber?.receive() } } } extension UITapGestureRecognizer { var tapGesturePublisher: AnyPublisher<UITapGestureRecognizer, Never> { gesturePublisher(for: self) } } private func gesturePublisher<Gesture: UIGestureRecognizer>(for gesture: Gesture) -> AnyPublisher<Gesture, Never> { Publishers.CustomTarget( control: gesture, addTargetAction: { gesture, target, action in gesture.addTarget(target, action: action)}, removeTargetAction: { gesture, target, action in gesture.removeTarget(target, action: action) }) .subscribe(on: DispatchQueue.main) .map { gesture } .eraseToAnyPublisher() }
以下是我自定义的UIView扩展代码:
extension UIView { var tapPublisher: AnyPublisher<UITapGestureRecognizer, Never> { // this is problem UITapGestureRecognizer().tapGesturePublisher } }
该手势识别器并未生效,请问我的代码存在什么问题?
问题分析与解决方案
核心问题点
- 手势未绑定到目标View:当前
tapPublisher仅创建了UITapGestureRecognizer实例,但未调用addGestureRecognizer(_:)将手势添加到当前UIView上,手势无法监听View的点击事件。 - 重复创建手势实例:每次访问
tapPublisher属性都会生成新的手势对象,不仅浪费资源,还会导致订阅逻辑混乱。 - 缺失手势生命周期管理:订阅取消时,仅移除了手势的target,但未将手势从View上移除,可能引发内存泄漏。
修正后的代码
修改UIView的扩展,确保手势被正确绑定到View、复用实例,同时在订阅取消时清理资源:
extension UIView { private struct AssociatedKeys { static var tapGestureKey = "tapGestureKey" } private var tapGesture: UITapGestureRecognizer { if let existingGesture = objc_getAssociatedObject(self, &AssociatedKeys.tapGestureKey) as? UITapGestureRecognizer { return existingGesture } let gesture = UITapGestureRecognizer() objc_setAssociatedObject(self, &AssociatedKeys.tapGestureKey, gesture, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) return gesture } var tapPublisher: AnyPublisher<UITapGestureRecognizer, Never> { // 确保View允许交互(部分UIView子类如UILabel默认关闭交互) self.isUserInteractionEnabled = true return Publishers.CustomTarget( control: tapGesture, addTargetAction: { [weak self] gesture, target, action in gesture.addTarget(target, action: action) self?.addGestureRecognizer(gesture) }, removeTargetAction: { [weak self] gesture, target, action in gesture.removeTarget(target, action: action) self?.removeGestureRecognizer(gesture) }) .subscribe(on: DispatchQueue.main) .map { self.tapGesture } .eraseToAnyPublisher() } }
关键改进说明
- 关联对象复用手势:通过
objc_getAssociatedObject为每个UIView实例绑定唯一的手势对象,避免重复创建。 - 绑定手势与View生命周期:在
addTargetAction中添加手势到View,removeTargetAction中从View移除手势,确保手势随订阅状态正确管理。 - 自动启用交互:设置
isUserInteractionEnabled = true,避免因View默认交互关闭导致手势不响应。
内容的提问来源于stack exchange,提问作者안은노
相关产品推荐
相关产品推荐

