iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 24
0
Mobile Development

RxSwift / 30天探索之旅系列 第 24

第 24 天 - Reactive Extensions

  • 分享至 

  • xImage
  •  

我們一路上使用各種Rx版的UIKit,其實都是透過Extension,今天就想來聊聊,到底要如何自訂Rx Extensions。

Binder

Binder是一種Observer,它有幾個特徵

  1. 不處理.error,只處理.next
  2. 預設在MainScheduler處理

我們以UIControl為例,打開UIControl+Rx.swift,第一段就是對isEnable做封裝

public var isEnabled: Binder<Bool> {
    return Binder(self.base) { control, value in
        control.isEnabled = value
    }
}

Binder在初始化時會需要給定一個closure,參數包含兩個,第一個是Target,也就是Base,以上範例就是UIControl,第二參數是Value

既然是Observer,只用起來會像這樣

Observable.of(true).bind(to: button.rx.isEnabled).disposed(by: disposeBag)

ControlEvent

ControlEvent是一種Observable的變化型,它有幾個特徵

  1. 不會產生error
  2. 在MainScheduler運行,同以下意思
Observable
    .subscribeOn(MainScheduler.instance) 
    .observeOn(MainScheduler.instance)

就以UIButton作為例子,打開UIButton+Rx.swift,第一段就是對於.touchUpInside事件的封裝

extension Reactive where Base: UIButton {
    
    /// Reactive wrapper for `TouchUpInside` control event.
    public var tap: ControlEvent<Void> {
        return controlEvent(.touchUpInside)
    }
}

其中Base就是代表我們要擴展的物件

// Reactive.swift
public struct Reactive<Base> {
    ...
}

既然ControlEvent是一種Observable的變化型,使用起來就可以像這樣串

button.rx.tap.bind(to: observer).disposed(by: disposeBag)

ControlProperty

ControlProperty可以是Observable,也可以是Observer,有幾個特徵

  1. 不處理.error
  2. 在MainScheduler運行
  3. 可共享,即是shareRelay(1)

就以UIButton作為例子,打開UISwitch+Rx.swift,第一段就是對isOn的封裝

// UISwitch+Rx.swift
public var isOn: ControlProperty<Bool> {
    return value
}

public var value: ControlProperty<Bool> {
    return base.rx.controlPropertyWithDefaultEvents(
        getter: { uiSwitch in
            uiSwitch.isOn
        }, setter: { uiSwitch, value in
            uiSwitch.isOn = value
        }
    )
}

其中controlPropertyWithDefaultEvents是個重點,它位於『UIControl+Rx.swift』,當.valueChanged時,執行getter或setter

// UIControl+Rx.swift
internal func controlPropertyWithDefaultEvents<T>(
    editingEvents: UIControl.Event = [.allEditingEvents, .valueChanged],
    getter: @escaping (Base) -> T,
    setter: @escaping (Base, T) -> Void
    ) -> ControlProperty<T> {
    return controlProperty(
        editingEvents: editingEvents,
        getter: getter,
        setter: setter
    )
}

因為被定義為internal,所以要擴充自訂的class時,無法直接參照上面程式碼,可以改成下面這樣

extension Reactive where Base: RadioButton {
    var isOn: ControlProperty<Bool> {
        return value
    }

    var value: ControlProperty<Bool> {
        return controlProperty(
            editingEvents: [.allEditingEvents, .valueChanged],
            getter: { radioButton in
                radioButton.isOn
            },
            setter: { radioButton, value in
                radioButton.isOn = value
            }
        )
    }
}

封裝Function

這部分就是第 4 天 - Observable (下)提過的Observable.create去實現,在『URLSession+Rx.swift』中可以看到範例

public func response(request: URLRequest) -> Observable<(response: HTTPURLResponse, data: Data)> {
    return Observable.create { observer in

        ...

        let task = self.base.dataTask(with: request) { data, response, error in

            ...

            observer.on(.next((httpResponse, data)))
            observer.on(.completed)
        }

        task.resume()

        return Disposables.create(with: task.cancel)
    }
}

自訂operator

這部分是從RxSwift: Reactive Programming with Swift書中章節所介紹,我覺得之後可能很有用,於是我將其簡化再簡化做個分享。假如在有一全域變數Cache,我能在任何為Observable<Int>的Observable使用此storeCache操作,將其存到Cache變數中,我可以這樣寫

var Cache: Int = 0

extension ObservableType where Element == Int {
    func storeCache() -> Observable<Element> {
        return self.do(onNext: {
            Cache = $0
        })
    }
}

用起來就會像這樣

Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance)
    .storeCache()
    .subscribe(onNext: { _ in
        print("Cache: \(Cache)")
    })
    .disposed(by: disposeBag)

倒數最後一週~明天是接續Reactive Extensions的延伸討論,明天見


上一篇
第 23 天 - RxBlocking & RxTest 範例
下一篇
第 25 天 - DelegateProxy
系列文
RxSwift / 30天探索之旅30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言