SwiftLee 是一位樂於分享的 iOS 開發者, 在 Combine 釋出沒多久, 就發佈了介紹 Combine 的系列文章, 更在 mobiconf 上主持 workshop 介紹如何用 Combine 製作 完整的 APP, 今天我要嘗試解讀他的文章: Creating a custom Combine Publisher to extend UIKit - SwiftLee, 在這篇文章中, SwiftLee 介紹了 UIKit 中缺少的 Event Publisher, 是一篇有趣且實用的文章!
UIControl 的 @IBAction
生命週期是與 Target 綁定的, 也因此失去 Combine 控制事件的特性, 所有我們必須自行建立控制事件的方法(如: .cancel()
), 透過 DI 進來的 UIControl, 我們指定這個 Subscription 作為 UIControl 的 Target, 並使 Action 發生時使 subscriber 接收到 object.
/// A custom subscription to capture UIControl target events.
final class UIControlSubscription<SubscriberType: Subscriber, Control: UIControl>: Subscription where SubscriberType.Input == Control {
private var subscriber: SubscriberType?
private let control: Control
init(subscriber: SubscriberType, control: Control, event: UIControl.Event) {
self.subscriber = subscriber
self.control = control
control.addTarget(self, action: #selector(eventHandler), for: event)
}
func request(_ demand: Subscribers.Demand) {
// We do nothing here as we only want to send events when they occur.
// See, for more info: https://developer.apple.com/documentation/combine/subscribers/demand
}
func cancel() {
subscriber = nil
}
@objc private func eventHandler() {
_ = subscriber?.receive(control)
}
}
注意 cancel()
做的事情其實不多, 其實只有讓系統回收 subscriber 而已.
在 Combine 有個特別的特性, 就是每個 Publisher 或是 Operator 都有自己的 Struct
, 為了符合這個特性, SwiftLee 建立了 UIControlPublisher:
struct UIControlPublisher<Control: UIControl>: Publisher {
typealias Output = Control
typealias Failure = Never
let control: Control
let controlEvents: UIControl.Event
init(control: Control, events: UIControl.Event) {
self.control = control
self.controlEvents = events
}
func receive<S>(subscriber: S) where S : Subscriber, S.Failure == UIControlPublisher.Failure, S.Input == UIControlPublisher.Output {
let subscription = UIControlSubscription(subscriber: subscriber, control: control, event: controlEvents)
subscriber.receive(subscription: subscription)
}
}
在這裡限制了 Output 與 Failure 的型別, 並將 Subscription 綁定至 UIControlSubscription.
完成了下游末端的接收之後, SwiftLee 接著針對上游設計, 使用 CombineCompatible
, 將 UIControl 拓展
protocol CombineCompatible { }
extension UIControl: CombineCompatible { }
extension CombineCompatible where Self: UIControl {
func publisher(for events: UIControl.Event) -> UIControlPublisher {
return UIControlPublisher(control: self, events: events)
}
}
let button = UIButton()
button.publisher(for: .touchUpInside).sink { button in
print("Button is pressed!")
}
button.sendActions(for: .touchUpInside)
如此一來, 就可以方便的使用 Publisher, 在 UIKit 擁抱 Combine!
出處: SwiftLee: https://www.avanderlee.com/
簡單測驗
拓展 UIView 的 UIGestureRecognizerPublisher
這個button 是不是少了 store?
不好意思,我沒有看懂你的意思。這裡我仔細看了一下,不太懂 store 指的是什麼意思。