【iOS】RxSwiftでFluxを実装する際のちょっと痒いところの改善案①


Fluxはデータフローを単一方向に限定するのが特徴的な設計パターンです。
SwiftでRxSwiftを利用してFluxを実現する際にに、下記のような問題点があると思います。

- Actionからsubscribeしたり、StoreからonNextができてしまう
- Storeが保持している値をViewからも変えることができてしまう

これらの問題を解決するためにはどのような実装にしていくかを解説していきたいと思います。

![flux.png](https://qiita-image-store.s3.amazonaws.com/0/60325/adb959fd-8b05-0d26-8323-954f5a8137ae.png)

# Actionからsubscribeしたり、StoreからonNextができてしまう

RxSwiftでイベントをDispatchするために、下記のように**PublishSubject**としてPropertyを定義するかと思います。

```swift:SearchDispatcher①
final class SearchDispatcher {
    static let shared = SearchDispatcher()

    let items = PublishSubject<[Item]>()
    let error = PublishSubject<Error>()
    let lastItemsRequest = PublishSubject<ItemsRequest>()
        
    init() {}
}
```
PublishSubjectであるということは

- **ObservableType**なので`func subscribe<O: ObserverType>(_ observer: O) -> Disposable where O.E == E`を呼べる
- **ObserverType**なので`func onNext(_ element: E)`を呼べる

ということになります。
つまり、Fluxのデーターフローは単方向であるにも関わらず

- **ActionからDispatcherにsubscribe**
- **StoreからDispatcherにonNext**

ができてしまうということになります。

## AnyDispatcherを利用して解決する

ActionからはonNext、Storeからはsubscribeしかできないという状態にするために、`Observer`なDispatcherと`Observable`なDispatcherのラッパーを実装していきます。

まずは**Dispatchable**というProtocolです。
Dispatchableで重要な部分は

- `AnyObserver<StateType>`型のobserverState
- `Observable<StateType>`型のobservableState

の実装を強制しているところです。
**StateType**はDispatchableを採用したオブジェクトで、enumになる想定をしています。
Protocolでinitializerをデフォルト実装することができないので、propertiesの中で必要なpropertyを生成しています。

```swift:Dispatchable
protocol Dispatchable {
    associatedtype StateType
    
    static var shared: Self { get }
    
    var observerState: AnyObserver<StateType> { get }
    var observableState: Observable<StateType> { get }
   
    init()
}

extension Dispatchable {
    static func properties() -> (observer: AnyObserver<StateType>, observable: Observable<StateType>) {
        let state = PublishSubject<StateType>()
        return (state.asObserver(), state)
    }
}
```

実際にDispatchableを採用した**SearchDispatcher**クラスは下記のようになります。
SearchDispatcher①でPublishSubjectとして定義していたPropertyを、StateとしてAssociated Value Enumにしています。
このenumを`AnyObserver<State>`、`Observable<State>`とすることで、DispatchableのStateTypeが確定します。
initializerでは、Dispatchableのpropertiesを使用して、observerStateとobservableStateを初期化します。

```swift:SearchDispatcher②
final class SearchDispatcher: Dispatchable {
    enum State {
        case items([Item])
        case error(Error)
        case lastItemsRequest(ItemsRequest)
    }
    
    static let shared = SearchDispatcher()
    
    let observerState: AnyObserver<State>
    let observableState: Observable<State>
    
    required init() {
        (self.observerState, self.observableState) = SearchDispatcher.properties()
    }
}
```

ここまでの実装では、observerとobservableにPropertyを分けただけで、PublishSubjectでPropertyを定義しているのと対して差はありません。
そこで、SearchDispatcher②を

- AnyObserverDispatcher...`Observer`なDispatcher
- AnyObservableDispatcher...`Observable`なDispatcher

として扱えるようにします。
AnyObserverDispatcherではAnyObserver<DispatcherType.StateType>型のstateを定義し、DispatcherTypeのobserverStateで初期化します。
AnyObservableDispatcherではObservable<DispatcherType.StateType>型のstateを定義し、DispatcherTypeのobservableStateで初期化します。
DispatcherType.StateTypeはDispatchableのStateTypeになります。
よって、SearchDispatcher②の場合、StateTypeはStateになります。

```swift:AnyDispatcher
final class AnyObserverDispatcher<DispatcherType: Dispatchable>: ObserverType {
    public typealias E = DispatcherType.StateType
    
    private let state: AnyObserver<E>
    
    init(_ dispatcher: DispatcherType = .shared) {
        self.state = dispatcher.observerState
    }
    
    public func on(_ event: Event<E>) {
        state.on(event)
    }
    
    func dispatch(_ value: E) {
        on(.next(value))
    }
}

final class AnyObservableDispatcher<DispatcherType: Dispatchable>: ObservableType {
    public typealias E = DispatcherType.StateType
    
    private let state: Observable<E>
    
    init(_ dispatcher: DispatcherType = .shared) {
        self.state = dispatcher.observableState
    }
    
    public func subscribe<O : ObserverType>(_ observer: O) -> Disposable where O.E == E {
        return state.subscribe(observer)
    }
}
```

それでは、AnyObserverDispatcherをActionで利用して、onNextだけが呼べるDispatcherとしていきます。
AnyObserverDispatcherのinitializerはDispatchableになっているので、SearchDispatcher②のインスタンスを渡します。
よって、searchDispatcherはsubscribeはできないDispatcherになります。

```swift:SearchAction
final class SearchAction {
    static let shared = SearchAction()
    
    private let searchDispatcher: AnyObserverDispatcher<SearchDispatcher>
    private let searchStore: SearchStore
    private let session: QiitaSession
    private let disposeBag = DisposeBag()
        
    init(
        searchDispatcher: AnyObserverDispatcher<SearchDispatcher> = .init(.shared),
        searchStore: SearchStore = .shared,
        session: QiitaSession = .shared
    ) {
        self.searchDispatcher = searchDispatcher
        self.searchStore = searchStore
        self.session = session
    }
    
    func search(query: String? = nil) {
        //〜〜
        let request = ItemsRequest(page: nextPage, perPage: perPage, query: nextQuery)

        //AnyObserverDispatcher<SearchDispatcher>なので、state.onNextなどのObserverTypeしか見えない
        searchDispatcher.dispatch(.lastItemsRequest(request))

        session.send(request)
            .subscribe(onNext: {
                //〜〜
            })
            .addDisposableTo(disposeBag)
    }
}
```

次に、AnyObservableDispatcherをStoreで利用して、subscribeだけが呼べるDispatcherとしていきます。
AnyObservableDispatcherのinitializerはDispatchableになっているので、SearchDispatcher②のインスタンスを渡します。
よって、searchDispatcherはonNextはできないDispatcherになります。

```swift:SearchStore
final class SearchStore {
    static let shared = SearchStore()
    
    let items = Variable<[Item]>([])
    let error = Variable<Error?>(nil)
    let lastItemsRequest = Variable<ItemsRequest?>(nil)
    private let disposeBag = DisposeBag()
    
    init(searchDispatcher: AnyObservableDispatcher<SearchDispatcher> = .init(.shared)) {
        
        //AnyObservableDispatcher<SearchDispatcher>なので、state.subscribeなでのObservableTypeしか見えない   
        searchDispatcher.subscribe(onNext: { [unowned self] state in
                //〜〜
            })
            .addDisposableTo(disposeBag)
    }
}
```

このように実装することによって、ActionからはonNext、Storeからはsubscribeしかできないという状態にでき、データーフローを単一方向に保つことができます。
この実装は、[RxAnyDispatcher](https://github.com/marty-suzuki/RxAnyDispatcher)としてGithubで公開しています。

# Storeが保持している値をViewから変えることができてしまう

先程のサンプルのようにStoreで値を保持するために、**Variable**としてPropertyを定義すると思います。

```swift:SearchStore①
final class SearchStore {
    static let shared = SearchStore()
    
    let items = Variable<[Item]>([])
    let error = Variable<Error?>(nil)
    let lastItemsRequest = Variable<ItemsRequest?>(nil)
    private let disposeBag = DisposeBag()
    
    init(searchDispatcher: AnyObservableDispatcher<SearchDispatcher> = .init(.shared)) {
        
        //AnyObservableDispatcher<SearchDispatcher>なので、state.subscribeなでのObservableTypeしか見えない   
        searchDispatcher.subscribe(onNext: { [unowned self] state in
                //〜〜
            })
            .addDisposableTo(disposeBag)
    }
}
```

Variableであるということは

```swift
SearchStore.shared.items.value = []
```

のように値を変更することができてしまいます。
つまり、Fluxのデーターフローは単方向であるにも関わらず、ViewからStoreの値を変更できてしまいます。

## [RxProperty](https://github.com/inamiy/RxProperty)を利用して解決する

[inamiy](https://github.com/inamiy)さんが公開している[RxProperty](https://github.com/inamiy/RxProperty)を利用することで、get-onlyな`SearchStore.shared.items.value`にすることができます。
例として、\_itemsをprivateなVariableとして定義しつつ、itemsをProperty型として定義します。
\_itemsは空の配列をVariableに渡して初期化をし、itemsは\_itemsをProperty型に渡して初期化をします。
searchDispatcherをsubscribeして受け取った値を\_items.valueで更新をし、Viewではitemsからsubscribeすることが可能になります。

```swift:SearchStore②
final class SearchStore {
    static let shared = SearchStore()
    
    let items: Property<[Item]>
    private let _items = Variable<[Item]>([])
    
    let error: Property<Error?>
    private let _error = Variable<Error?>(nil)
    
    let lastItemsRequest: Property<ItemsRequest?>
    private let _lastItemsRequest = Variable<ItemsRequest?>(nil)
    
    private let disposeBag = DisposeBag()
    
    init(searchDispatcher: AnyObservableDispatcher<SearchDispatcher> = .init(.shared)) {
        self.items = Property(_item)
        self.error = Property(_error)
        self.lastItemsRequest = Property(_lastItemsRequest)
        
        searchDispatcher.subscribe(onNext: { [unowned self] state in
                switch state {
                case .items(let value):
                    if value.isEmpty {
                        self._item.value.removeAll()
                    } else {
                        self._item.value.append(contentsOf: value)
                    }
                case .error(let value):
                    self._error.value = value
                    
                case .lastItemsRequest(let value):
                    self._lastItemsRequest.value = value
                }
            })
            .addDisposableTo(disposeBag)
    }
}
```

このように実装することによって、Viewからはsubscribeしかできないという状態にでき、データーフローを単一方向に保つことができます。

さらに詳しい内容については4/26(水)に開催される[『モバイルアプリ開発エキスパート養成読本』出版記念 Tech Talks](https://connpass.com/event/50979/)でLTする予定です。

## 2017/04/06追記

こちらの記事は複数のDispatcherを想定したものになります。
Dispathcher自体を1つにする場合の改善案は、[【iOS】FluxのDispatcherを1つでデータフローを単一方向に保つ案](http://qiita.com/marty-suzuki/items/fb597b0ac47bf794cf78)に記載しています。