【iOS】FluxのDispatcherを1つでデータフローを単一方向に保つ案②

以前に[【iOS】RxSwiftでFluxを実装する際のちょっと痒いところの改善案](http://qiita.com/marty-suzuki/items/36097e54066d1ec9d59c)でFluxのデータフローを単一方向に保つ改善案を書いてみました。
そちらでは、ActionとStore1つ実装すると、Dispatcherを1つ実装する想定でした。
今回は、Dispatcherが1つだった場合にデータフローを単一方向に保つ方法を解説していきたいと思います。

## Dispatcherの実装

今回の場合、Dispatcherは1つになるので、実装しているStoreの個数分の`ChildDispatcher`のPropertyを定義します。
`ChildDispatcher`は何かというと、_observerとobservableを保持したGenerics class_です。
Generics classとすることで、Dispatcherのextensionで定義している`Dispatcher.Search`や`Dispatcher.Message`を、observerとobservableを初期化する際の`PublishSubject<Element>`で利用できるようにしています。
Dispatcherで保持しているChildDispatcherのPropertyは、それぞれ1つずつのみインスタンス化されるようにしたいため、initializerがfileprivateになっています。

```swift:Dispatcher.swift
final class Dispatcher {
    static let shared = Dispatcher()
    
    let search = ChildDispatcher<Search>()
    let message = ChildDispatcher<Message>()
    
    private init() {}
}

final class ChildDispatcher<Element> {
    let observer: AnyObserver<Element>
    let observable: Observable<Element>
    
    fileprivate init() {
        let element = PublishSubject<Element>()
        self.observer = element.asObserver()
        self.observable = element
    }
}
```

`Dispatcher.Search`や`Dispatcher.Message`の列挙子は、_それぞれを１つずつのDispatcherとした場合に定義することになるProperty_です。

```swift:Dispatcher.Search.swift
extension Dispatcher {
    enum Search {
        case items([Item])
        case error(Error)
        case lastItemsRequest(ItemsRequest)
    }
}
```

```Dispatcher.Message.swift
extension Dispatcher {
    enum Message {
        case messages([Message])
        case error(Error)
        case lastOffset(Int)
    }
}
```

## ActionやStoreでそれぞれに不要なものを隠蔽するDispatcherのラッパー

Storeからregisterのみを呼び出せるようにするため、RegisterableDispatcherを実装します。
RegisterableDispatcherのType parameterを\<E, C: ChildDispatcher\<E\>\>とし、Dispatcher.SearchやDispatcher.Messageを利用できるようにします。
initializerの引数としてChildDispatcherを受け取り、`self.observable`をChildDispatcherのobservableで初期化します。
`func register(_ using: ((E) -> Void)?) -> Disposable`を呼ぶと、そのobservableをsubscribeしています。
同様にして、Actionからdispatchのみを呼び出せるようにするため、DispatchableDispatcherを実装します。
initializerの引数としてChildDispatcherを受け取り、`self.observer`をChildDispatcherのobserverで初期化します。
`func dispatch(_ element: E)`を呼ぶと、そのobserverでonNextしています。

```swift:Dispatcher.swift
final class RegisterableDispatcher<E, C: ChildDispatcher<E>> {
    private let observable: Observable<E>
    
    init(_ child: C) {
        self.observable = child.observable
    }
    
    func register(_ using: ((E) -> Void)?) -> Disposable {
        return observable.subscribe(onNext: using)
    }
}

final class DispatchableDispatcher<E, C: ChildDispatcher<E>> {
    private let observer: AnyObserver<E>
    
    init(_ child: C) {
        self.observer = child.observer
    }
    
    func dispatch(_ element: E) {
        observer.onNext(element)
    }
}
```

## 利用例

実際にSearchActionで`DispatchableDispatcher<Dispatcher.Search>`のProeprtyを持ち、初期化時に`Dispatcher.shared.search`を引数として渡すことで、**dispatchのみを行うことができるDispatcher**として利用できるようになります。

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

    private let searchDispatcher: DispatchableDispatcher<Dispatcher.Search>
    private let searchStore: SearchStore
    private let session: QiitaSession
    private let disposeBag = DisposeBag()

    init(
        searchDispatcher: DispatchableDispatcher<Dispatcher.Search> = .init(Dispatcher.shared.search),
        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)

        //DispatchableDispatcher<Dispatcher.Search>なので、dispatchしか見えない
        searchDispatcher.dispatch(.lastItemsRequest(request))

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

同様にSearchStoreで`RegisterableDispatcher<Dispatcher.Search>`のProeprtyを持ち、初期化時に`Dispatcher.shared.search`を引数として渡すことで、**registerのみを行うことができる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: RegisterableDispatcher<Dispatcher.Search> = .init(Dispatcher.shared.search)) {

        //RegisterableDispatcher<Dispatcher.Search>なので、registerしか見えない   
        searchDispatcher.register { [unowned self] element in
                //〜〜
            }
            .addDisposableTo(disposeBag)
    }
}
```