iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 20
1
自我挑戰組

30 天了解 Swift 的 Combine系列 第 20

30 天了解 Swift 的 Combine: [20] Combine 好文分享: Custom publisher in UIControl

  • 分享至 

  • xImage
  •  

SwiftLee 是一位樂於分享的 iOS 開發者, 在 Combine 釋出沒多久, 就發佈了介紹 Combine 的系列文章, 更在 mobiconf 上主持 workshop 介紹如何用 Combine 製作 完整的 APP, 今天我要嘗試解讀他的文章: Creating a custom Combine Publisher to extend UIKit - SwiftLee, 在這篇文章中, SwiftLee 介紹了 UIKit 中缺少的 Event Publisher, 是一篇有趣且實用的文章!

1. 介紹 Custom Subscription

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 而已.

2. 建立 UIControlPublisher

在 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.

3. 套用至 UIControl

完成了下游末端的接收之後, 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


上一篇
30 天了解 Swift 的 Combine: [19] 使用 @Published 改寫 Day 18
下一篇
30 天了解 Swift 的 Combine: [21] Combine 的錯誤處理
系列文
30 天了解 Swift 的 Combine30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
Alvin
iT邦新手 4 級 ‧ 2021-09-11 10:05:04

這個button 是不是少了 store?

ytyubox iT邦新手 5 級 ‧ 2021-12-26 09:11:14 檢舉

不好意思,我沒有看懂你的意思。這裡我仔細看了一下,不太懂 store 指的是什麼意思。

我要留言

立即登入留言