iOSアプリ開発でもFluxしたい
WebアプリケーションのフロントエンドをReact + Fluxな構成で開発すると、若干コードが増えて面倒にはなりつつも、
ビューの状態やデータの流れが明示的になってとてもわかりやすいなと改めて感じてます。
iOSアプリでもこういうことがやりたいのですが、基本的にFluxは考え方に過ぎないので必要な実装は自分でやらないといけません。
なので作ってみました。
https://github.com/yonekawa/SwiftFlux
使い方
基本的には必要なAction protocolとStore protocolを実装したクラスを作り、DispatcherとEventEmitterを使ってView - Action - Store - View の単方向データフローを作るだけです。詳しくはREADMEをご覧ください。
//: Step 1: Actionを定義
class TodoAction {
class Create : Action {
typealias Payload = Todo
func invoke() {
let todo = Todo(title: "New ToDo")
Dispatcher.dispatch(self, result: Result(value: todo))
}
}
}
//: Step 2: Storeを定義してdispatchされたいActionにDispatcher.registerする
class TodoStore : Store {
static let instance = TodoStore()
enum TodoEvent {
case Created
}
typealias Event = TodoEvent
var todos = [Todo]()
var list: Array<Todo> {
get {
return todos;
}
}
init() {
Dispatcher.register(TodoAction.List()) { (result) -> Void in
switch result {
case .Success(let box):
self.todo = box.value
EventEmitter.emit(self, event: TodoEvent.Created)
case .Failure(let box):
break;
}
}
}
}
//: Step 3: ViewController側でEventをlistenする
EventEmitter.listen(TodoStore.instance, event: TodoStore.Event.List) { () -> Void in
for todo in TodoStore.instance.list {
plintln(todo.title)
}
}
//: Step4: ユーザーのインタラクションに応じてActionをinvokeする
TodoAction.List().invoke()
何が嬉しいの?
JavaScriptのように型が無い言語におけるFlux実装では、Actionから何がStoreに流れてくるのかは実装を見ないとわからず、
SwiftFluxではSwiftのジェネリクスやenumを使って、型安全なFluxを実現するように設計されています。
例えば上記のコード例だと、TodoAction.Create
アクションが実行されて、Storeに渡ってくる値はtypealias
とジェネリクスによって必ずTodo
オブジェクトであることが示されます。
型安全であることにより、Xcodeのコード補間が強力に動作します。Dispatcher.dispatch
に渡す値はTodo
であることがわかるため、コード補間やコンパイラによるチェックによって迷うことなくコードを書くことができます。
また、よくあるFlux実装ではdispatchされたブロックでActionのTypeによってswitchで処理を分岐することが多いですが、registerするActionを指定するようなインタフェースにすることでブロックに渡ってくる結果が自明になりました。
結果にはantitypical/Resultを使うことで、アクションが成功した時と失敗した時の型をより明確に示すことができます。
実装にあたってはAPIKitの設計を大いに参考にしました。
今後の予定
直近では、Swift2 にすれば Dispatcher
もプロトコルベースにしてデフォルト実装を提供すればアプリの性質によって実装を差しかえることもやりやすくなりそうなので対応したいなと思っています。
また、EventEmitter
は型を重視してStoreのEventしかlistenできなくしてあるので、Swift2化したらこれはStoreのデフォルト実装にしたいです。
Dispatcher.register
に対してActionのインスタンスが必要になるのが微妙だと思っていて、ここはなんとかしたいところです。
とりあえず自分で使いたいがために作ったので、まずはこれをベースにアプリを書いてみて徐々に改善していきたいと思ってます。