如何在Combine与UIKit中为UIButton创建类似RxSwift效果的Publisher?
完全可以通过扩展UIControl的方式实现你要的调用效果,实现步骤如下:
1. 先实现通用的UIControl事件Publisher
这段代码可以复用给所有UIControl子类的事件监听,后续开发其他控件的事件监听也可以直接使用:
import Combine import UIKit extension UIControl { struct EventPublisher: Publisher { typealias Output = Void typealias Failure = Never private let control: UIControl private let event: Event init(control: UIControl, event: Event) { self.control = control self.event = event } func receive<S>(subscriber: S) where S : Subscriber, Never == S.Failure, Void == S.Input { let subscription = EventSubscription(subscriber: subscriber, control: control, event: event) subscriber.receive(subscription: subscription) } } private class EventSubscription<S: Subscriber>: Subscription where S.Input == Void, S.Failure == Never { private var subscriber: S? private weak var control: UIControl? private let event: UIControl.Event init(subscriber: S, control: UIControl, event: UIControl.Event) { self.subscriber = subscriber self.control = control self.event = event control.addTarget(self, action: #selector(eventTriggered), for: event) } func request(_ demand: Subscribers.Demand) {} func cancel() { control?.removeTarget(self, action: #selector(eventTriggered), for: event) subscriber = nil control = nil } @objc private func eventTriggered() { _ = subscriber?.receive(()) } } func publisher(for event: Event) -> EventPublisher { EventPublisher(control: self, event: event) } }
2. 给UIButton添加类Rx风格的调用入口
这里实现类似RxSwift中.rx的命名空间,你可以自定义命名,示例用cb(Combine缩写):
extension UIButton { var cb: CBNamespace { CBNamespace(button: self) } struct CBNamespace { private let button: UIButton init(button: UIButton) { self.button = button } /// 对应按钮点击事件touchUpInside var tap: AnyPublisher<Void, Never> { button.publisher(for: .touchUpInside) .eraseToAnyPublisher() } } }
3. 最终调用效果
和你示例的RxSwift写法几乎一致:
// 你需要在当前类持有一个Set<AnyCancellable>用来保存订阅生命周期 private var cancellables = Set<AnyCancellable>() button.cb.tap .sink { [weak self] _ in // 处理按钮点击逻辑 } .store(in: &cancellables)
如果你不需要和RxSwift风格对齐,也可以直接调用`button.publisher(for: .touchUpInside).sink(...)实现点击监听,不需要额外加命名空间。
内容的提问来源于stack exchange,提问作者Kou Yamamoto
相关产品推荐
相关产品推荐

