ReSwiftのステートをRxSwiftを使って監視する
普段から RxSwift(Reactive ExtensionsのSwift実装)と ReSwift(ReduxのSwift実装)を使っています。RxSwift を通信系の処理だったりデリゲートの処理(例えば StoreKit とか CoreBluetooth)だったりに使って、ReSwiftをアプリ全体で管理しなきゃいけないデータの監視なんかに使ってます。ReSwift をそのまま使ってもあまり困らないのですが、ReSwift のステートを Observable に変換してUI部品とバインドするといろいろと捗るなぁというわけで ReSwift の Rx 用ラッパークラスを作成しました。
ReduxのステートをRx化するクラス
Redux のステートを Rx の Variable に格納して、Redux からステート変更の通知が来たら Variable(エラーの発生しないBehaviorSubject)を更新するクラスを作成します。
import RxSwift import ReSwift class RxStore<AppStateType: StateType>: StoreSubscriber { // 監視する値 let state: Variable<AppStateType> private let store: Store<AppStateType> init(store: Store<AppStateType>) { self.store = store self.state = Variable(store.state) // 購読開始 self.store.subscribe(self) } deinit { // 購読終了 self.store.unsubscribe(self) } func newState(state: AppStateType) { // Variableを更新する self.state.value = state } }
使い方
Store を生成します。ここは通常の ReSwift の使い方そのままです。
// ストア struct AppState: StateType { var count: Int = 0 } // アクション enum AppAction: Action { case incAction } // Reducer struct AppReducer: Reducer { func handleAction(action: Action, state: AppState?) -> AppState { let state = state ?? AppState() return AppState(count: state.count + 1) } } // ストアオブジェクトの生成 let mainStore = Store<AppState>( reducer: AppReducer(), state: nil )
ViewController で Rx化したステートを subscribe(購読)する場合は以下のようになります。
import UIKit import RxSwift import ReSwift class ViewController: UIViewController { let disposeBag = DisposeBag() let rxStore = RxStore<AppState>(store: mainStore) override func viewDidLoad() { super.viewDidLoad() rxStore.state.asObservable() .subscribe(onNext: { state in // 更新された時の処理 print(state.count) }).disposed(by: disposeBag) } } // ディスパッチ mainStore.dispatch(AppAction.incAction) mainStore.dispatch(AppAction.incAction) mainStore.dispatch(AppAction.incAction) mainStore.dispatch(AppAction.incAction)
UI部品とバインド
Rx化されて入れば UI 部品と簡単にバインドできます。
class ViewController: UIViewController { let disposeBag = DisposeBag() @IBOutlet weak var button: UIButton! override func viewDidLoad() { super.viewDidLoad() // カウントが3を越えたらtrueにする let appStateValid = rxStore.state.asObservable() .map { state -> Bool in return state.count > 3 }.shareReplayLatestWhileConnected() // ボタンのEnabledとバインドする appStateValid .bind(to: button.rx.isEnabled) .addDisposableTo(disposeBag) } }