A Day In The Life

とあるプログラマの備忘録

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)
  }
}

参考記事