You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Combine与UIKit中为UIButton创建类似RxSwift效果的Publisher?

UIButton 支持 Combine 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 18:15:02