flux with rxswift

85
Flux with RxSwift AbemaTV Developer Conference 2016 Yuji Hato

Upload: yuji-hato

Post on 07-Jan-2017

2.125 views

Category:

Software


0 download

TRANSCRIPT

Page 1: Flux with RxSwift

Flux with RxSwift

AbemaTV Developer Conference 2016

Yuji Hato

Page 2: Flux with RxSwift

About me

Yuji HatoCyberAgent, Inc. / AbemaTV, Inc.

dekatotoro

@dekatotoro

Contributed services

Page 3: Flux with RxSwift

What is Flux?

Page 4: Flux with RxSwift

What is Flux?

https://facebook.github.io/flux/docs/overview.html

“Data in a Flux application flows in a single direction”

Page 5: Flux with RxSwift

What is Flux?

https://github.com/facebook/flux

Page 6: Flux with RxSwift

What is Flux?

https://github.com/facebook/flux

Page 7: Flux with RxSwift

What is Flux?

https://github.com/facebook/flux

Observer パターン !

Page 8: Flux with RxSwift

Why Flux?

Page 9: Flux with RxSwift

Why Flux?

昨今のアプリ開発は複雑化の一途を辿っており状態管理が大変

Page 10: Flux with RxSwift

Why Flux?AbemaTVの状態管理 :

Cast

オンデマンド課金

CM

Filler

画質

視聴予約 etc…

番組 Fresh

コメント視聴数

FullScreen

Feed

Page 11: Flux with RxSwift

Why Flux?

つらい…

Page 12: Flux with RxSwift

Why Flux?

UI 操作・時間に伴う複雑な状態遷移を分かりやすくしたい

Page 13: Flux with RxSwift

Why Flux?

View 間の依存関係を減らしたい

Page 14: Flux with RxSwift

MVVMMVC

DDD

Flux

Clean ArchitectureMVP

Why Flux?

Flux が向いてそう… ?

Page 15: Flux with RxSwift

Flux with RxSwift

Page 16: Flux with RxSwift

Flux with RxSwift

Page 17: Flux with RxSwift

Flux with RxSwift

Event Stream で繋いじゃおうぜ

Page 18: Flux with RxSwift

Flux with RxSwift

Page 19: Flux with RxSwift

Flux with RxSwift

Eventは2種類の Hot Observableを使う

Page 20: Flux with RxSwift

Flux with RxSwift

PublishSubject

一切キャッシュしない Subject

Page 21: Flux with RxSwift

Flux with RxSwift

Variable

直近の値を1つだけキャッシュする Subject※BehaviorSubjectのwrapper

Page 22: Flux with RxSwift

Dispatcher

Page 23: Flux with RxSwift

Dispatcher

Page 24: Flux with RxSwift

DispatcherDispatcherのフロー

Page 25: Flux with RxSwift

DispatcherActionが dispatchする

Page 26: Flux with RxSwift

Dispatcherdispatchされたら Storeへ通知

Page 27: Flux with RxSwift

Dispatcher

class DispatchSubject<Element>: ObservableType, ObserverType { typealias E = Element fileprivate let subject = PublishSubject<E>() func dispatch(_ value: E) { on(.next(value)) } func on(_ event: Event<E>) { subject.on(event) }

func subscribe<O: ObserverType>(_ observer: O) -> Disposable where O.E == E { return subject.subscribe(observer) }}

Dispatcher用の DispatchSubject

Page 28: Flux with RxSwift

Dispatcher

class DispatchSubject<Element>: ObservableType, ObserverType { typealias E = Element fileprivate let subject = PublishSubject<E>() func dispatch(_ value: E) { on(.next(value)) } func on(_ event: Event<E>) { subject.on(event) }

func subscribe<O: ObserverType>(_ observer: O) -> Disposable where O.E == E { return subject.subscribe(observer) }}

PublishSubjectのWrapper

Page 29: Flux with RxSwift

Dispatcher

class DispatchSubject<Element>: ObservableType, ObserverType { typealias E = Element fileprivate let subject = PublishSubject<E>() func dispatch(_ value: E) { on(.next(value)) } func on(_ event: Event<E>) { subject.on(event) }

func subscribe<O: ObserverType>(_ observer: O) -> Disposable where O.E == E { return subject.subscribe(observer) }}

dispatchすると eventを発行します

Page 30: Flux with RxSwift

Dispatcher

class SomeDispatcher { static let shared = SomeDispatcher() let loading = DispatchSubject<Bool>() let error = DispatchSubject<Error>() let someModel = DispatchSubject<SomeModel>()

…}

Dispatcherクラス

Page 31: Flux with RxSwift

Dispatcher

class SomeDispatcher { static let shared = SomeDispatcher() let loading = DispatchSubject<Bool>() let error = DispatchSubject<Error>() let someModel = DispatchSubject<SomeModel>()

…}

ActionTypeの代わりに DispatchSubjectを複数用意※Dispatcherクラスも用途ごとに分けてます

Page 32: Flux with RxSwift

Dispatcher

func someAction(value: Bool) { … dispatcher.loading.dispatch(value) … }

Actionが dispatch

Page 33: Flux with RxSwift

Action

Page 34: Flux with RxSwift

Action

Page 35: Flux with RxSwift

ActionActionのフロー

Page 36: Flux with RxSwift

ActionViewから Actionを実行

Page 37: Flux with RxSwift

Action必要なデータを取得する

Page 38: Flux with RxSwift

ActionUI, Web, DB, Devicesを外部 IFとして捉える

https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html

Page 39: Flux with RxSwift

Actionデータの取得後 Dispatcherへ流す

Page 40: Flux with RxSwift

Action

func someAction(query: String) { dispatcher.loading.dispatch(true)

API.getSome(with: query) .do(onError: { [unowned self] error in self.dispatcher.error.dispatch(error) self.dispatcher.loading.dispatch(false) }) .do(onCompleted: { [unowned self] error in self.dispatcher.loading.dispatch(false) }) .subscribe(onNext: { [unowned self] response in let someModel = SomeModel.make(from: response) self.dispatcher.someModel.dispatch(someModel) }) .addDisposableTo(disposeBag) }

APIを実行する例

Page 41: Flux with RxSwift

Action

func someAction(query: String) { dispatcher.loading.dispatch(true)

API.getSome(with: query) .do(onError: { [unowned self] error in self.dispatcher.error.dispatch(error) self.dispatcher.loading.dispatch(false) }) .do(onCompleted: { [unowned self] error in self.dispatcher.loading.dispatch(false) }) .subscribe(onNext: { [unowned self] response in let someModel = SomeModel.make(from: response) self.dispatcher.someModel.dispatch(someModel) }) .addDisposableTo(disposeBag) }

ローディングして API実行

Page 42: Flux with RxSwift

Action

func someAction(query: String) { dispatcher.loading.dispatch(true)

API.getSome(with: query) .do(onError: { [unowned self] error in self.dispatcher.error.dispatch(error) self.dispatcher.loading.dispatch(false) }) .do(onCompleted: { [unowned self] error in self.dispatcher.loading.dispatch(false) }) .subscribe(onNext: { [unowned self] response in let someModel = SomeModel.make(from: response) self.dispatcher.someModel.dispatch(someModel) }) .addDisposableTo(disposeBag) }

エラーの時

Page 43: Flux with RxSwift

Action

func someAction(query: String) { dispatcher.loading.dispatch(true)

API.getSome(with: query) .do(onError: { [unowned self] error in self.dispatcher.error.dispatch(error) self.dispatcher.loading.dispatch(false) }) .do(onCompleted: { [unowned self] error in self.dispatcher.loading.dispatch(false) }) .subscribe(onNext: { [unowned self] response in let someModel = SomeModel.make(from: response) self.dispatcher.someModel.dispatch(someModel) }) .addDisposableTo(disposeBag) }

レスポンスが返ってきた時

Page 44: Flux with RxSwift

Action

func someAction(query: String) { dispatcher.loading.dispatch(true)

API.getSome(with: query) .do(onError: { [unowned self] error in self.dispatcher.error.dispatch(error) self.dispatcher.loading.dispatch(false) }) .do(onCompleted: { [unowned self] error in self.dispatcher.loading.dispatch(false) }) .subscribe(onNext: { [unowned self] response in let someModel = SomeModel.make(from: response) self.dispatcher.someModel.dispatch(someModel) }) .addDisposableTo(disposeBag) }

実行が完了した時

Page 45: Flux with RxSwift

Store

Page 46: Flux with RxSwift

Store

Page 47: Flux with RxSwift

StoreStoreのフロー

Page 48: Flux with RxSwift

StoreDispatcherの eventを Observeする

Page 49: Flux with RxSwift

StoreDispatcherのイベントが流れてくる

Page 50: Flux with RxSwift

StoreDispatcherのイベントを検知したらデータを更新

Page 51: Flux with RxSwift

Storeデータを更新したら Viewへ通知

Page 52: Flux with RxSwift

Flux: Store

class Store { let disposeBag = DisposeBag() func bind<O, E>(_ observable: O, _ param: Variable<E>) where O: ObservableType, E == O.E { observable.bindTo(param).addDisposableTo(disposeBag) }

func bind<O, E>(_ observable: O, _ param: PublishSubject<E>) where O: ObservableType, E == O.E { observable.bindTo(param).addDisposableTo(disposeBag) }}

Store親クラス

Page 53: Flux with RxSwift

Flux: Store

class Store { let disposeBag = DisposeBag() func bind<O, E>(_ observable: O, _ param: Variable<E>) where O: ObservableType, E == O.E { observable.bindTo(param).addDisposableTo(disposeBag) }

func bind<O, E>(_ observable: O, _ param: PublishSubject<E>) where O: ObservableType, E == O.E { observable.bindTo(param).addDisposableTo(disposeBag) }}

Variable用の bind

Page 54: Flux with RxSwift

Flux: Store

class Store { let disposeBag = DisposeBag() func bind<O, E>(_ observable: O, _ param: Variable<E>) where O: ObservableType, E == O.E { observable.bindTo(param).addDisposableTo(disposeBag) }

func bind<O, E>(_ observable: O, _ param: PublishSubject<E>) where O: ObservableType, E == O.E { observable.bindTo(param).addDisposableTo(disposeBag) }}

PublishSubject用の bind

Page 55: Flux with RxSwift

Flux: Store

class SomeStore: Store {

static let shared = SomeStore() let loading = Variable<Bool>(false) let error = PublishSubject<Error>() let someModel = Variable<SomeModel>(SomeModel())

init(dispatcher: SomeDispatcher = .shared) { super.init() bind(dispatcher.loading, loading) bind(dispatcher.error, error) bind(dispatcher.someModel, someModel) }}

Storeクラス

Page 56: Flux with RxSwift

Flux: Store

class SomeStore: Store {

static let shared = SomeStore() let loading = Variable<Bool>(false) let error = PublishSubject<Error>() let someModel = Variable<SomeModel>(SomeModel()) init(dispatcher: SomeDispatcher = .shared) { super.init() bind(dispatcher.loading, loading) bind(dispatcher.error, error) bind(dispatcher.someModel, someModel) }}

Storeクラスは Singleton

Page 57: Flux with RxSwift

Flux: Store

class SomeStore: Store {

static let shared = SomeStore() let loading = Variable<Bool>(false) let error = PublishSubject<Error>() let someModel = Variable<SomeModel>(SomeModel())

init(dispatcher: SomeDispatcher = .shared) { super.init() bind(dispatcher.loading, loading) bind(dispatcher.error, error) bind(dispatcher.someModel, someModel) }}

Storeの propertyは Variableと PublishSubject

Page 58: Flux with RxSwift

Flux: Store

class SomeStore: Store {

static let shared = SomeStore() let loading = Variable<Bool>(false) let error = PublishSubject<Error>() let someModel = Variable<SomeModel>(SomeModel()) init(dispatcher: SomeDispatcher = .shared) { super.init() bind(dispatcher.loading, loading) bind(dispatcher.error, error) bind(dispatcher.someModel, someModel) }}

initで dispatcherと bind

Page 59: Flux with RxSwift

View

Page 60: Flux with RxSwift

View

Page 61: Flux with RxSwift

ViewViewのフロー

Page 62: Flux with RxSwift

ViewStoreと UIの eventを Observeする

Page 63: Flux with RxSwift

ViewStoreのイベントが流れてくる

Page 64: Flux with RxSwift

ViewStoreのイベントを検知したら Viewを更新

Page 65: Flux with RxSwift

ViewUIの eventをトリガーに Actionを実行

Page 66: Flux with RxSwift

View store.loading.asObservable() .distinctUntilChanged() .observeOn(MainScheduler.instance) .subscribe(onNext: { [unowned self] loading in self.loadingView.hidden(!loading) }) .addDisposableTo(rx_disposeBag) store.error .subscribe(onNext: { error in ErrorAction.show(.apiError(error)) }) .addDisposableTo(rx_disposeBag)

store.someModel.asObservable() .map { $0.dataSource } .observeOn(MainScheduler.instance) .subscribe(onNext: { [unowned self] dataSource in self.dataSource.value = dataSource self.tableView?.reloadData() }) .addDisposableTo(rx_disposeBag)

Storeの observe

Page 67: Flux with RxSwift

View store.loading.asObservable() .distinctUntilChanged() .observeOn(MainScheduler.instance) .subscribe(onNext: { [unowned self] loading in self.loadingView.hidden(!loading) }) .addDisposableTo(rx_disposeBag) store.error .subscribe(onNext: { error in ErrorAction.show(.apiError(error)) }) .addDisposableTo(rx_disposeBag)

store.someModel.asObservable() .map { $0.dataSource } .observeOn(MainScheduler.instance) .subscribe(onNext: { [unowned self] dataSource in self.dataSource.value = dataSource self.tableView?.reloadData() }) .addDisposableTo(rx_disposeBag)

Storeの loadingを observe

Page 68: Flux with RxSwift

View store.loading.asObservable() .distinctUntilChanged() .observeOn(MainScheduler.instance) .subscribe(onNext: { [unowned self] loading in self.loadingView.hidden(!loading) }) .addDisposableTo(rx_disposeBag) store.error .subscribe(onNext: { error in ErrorAction.show(.apiError(error)) }) .addDisposableTo(rx_disposeBag)

store.someModel.asObservable() .map { $0.dataSource } .observeOn(MainScheduler.instance) .subscribe(onNext: { [unowned self] dataSource in self.dataSource.value = dataSource self.tableView?.reloadData() }) .addDisposableTo(rx_disposeBag)

loadingViewの表示 /非表示

Page 69: Flux with RxSwift

View store.loading.asObservable() .distinctUntilChanged() .observeOn(MainScheduler.instance) .subscribe(onNext: { [unowned self] loading in self.loadingView.hidden(!loading) }) .addDisposableTo(rx_disposeBag) store.error .subscribe(onNext: { error in ErrorAction.show(.apiError(error)) }) .addDisposableTo(rx_disposeBag)

store.someModel.asObservable() .map { $0.dataSource } .observeOn(MainScheduler.instance) .subscribe(onNext: { [unowned self] dataSource in self.dataSource.value = dataSource self.tableView?.reloadData() }) .addDisposableTo(rx_disposeBag)

Storeの errorを observe

Page 70: Flux with RxSwift

View store.loading.asObservable() .distinctUntilChanged() .observeOn(MainScheduler.instance) .subscribe(onNext: { [unowned self] loading in self.loadingView.hidden(!loading) }) .addDisposableTo(rx_disposeBag) store.error .subscribe(onNext: { error in ErrorAction.show(.apiError(error)) }) .addDisposableTo(rx_disposeBag)

store.someModel.asObservable() .map { $0.dataSource } .observeOn(MainScheduler.instance) .subscribe(onNext: { [unowned self] dataSource in self.dataSource.value = dataSource self.tableView?.reloadData() }) .addDisposableTo(rx_disposeBag)

ErrorActionを実行

Page 71: Flux with RxSwift

View store.loading.asObservable() .distinctUntilChanged() .observeOn(MainScheduler.instance) .subscribe(onNext: { [unowned self] loading in self.loadingView.hidden(!loading) }) .addDisposableTo(rx_disposeBag) store.error .subscribe(onNext: { error in ErrorAction.show(.apiError(error)) }) .addDisposableTo(rx_disposeBag)

store.someModel.asObservable() .map { $0.dataSource } .observeOn(MainScheduler.instance) .subscribe(onNext: { [unowned self] dataSource in self.dataSource.value = dataSource self.tableView?.reloadData() }) .addDisposableTo(rx_disposeBag)

Storeの someModelを observe

Page 72: Flux with RxSwift

View store.loading.asObservable() .distinctUntilChanged() .observeOn(MainScheduler.instance) .subscribe(onNext: { [unowned self] loading in self.loadingView.hidden(!loading) }) .addDisposableTo(rx_disposeBag) store.error .subscribe(onNext: { error in ErrorAction.show(.apiError(error)) }) .addDisposableTo(rx_disposeBag)

store.someModel.asObservable() .map { $0.dataSource } .observeOn(MainScheduler.instance) .subscribe(onNext: { [unowned self] dataSource in self.dataSource.value = dataSource self.tableView?.reloadData() }) .addDisposableTo(rx_disposeBag)

tableViewを reload

Page 73: Flux with RxSwift

View

searchBar.rx.text.asDriver() .throttle(0.3) .distinctUntilChanged() .drive(onNext: { query in SomeAction.someAction(query: query) }) .addDisposableTo(rx_disposeBag)

UIの eventを observe

Page 74: Flux with RxSwift

View

searchBar.rx.text.asDriver() .throttle(0.3) .distinctUntilChanged() .drive(onNext: { query in SomeAction.someAction(query: query) }) .addDisposableTo(rx_disposeBag)

searchBarの text入力を observe

Page 75: Flux with RxSwift

View

searchBar.rx.text.asDriver() .throttle(0.3) .distinctUntilChanged() .drive(onNext: { query in SomeAction.someAction(query: query) }) .addDisposableTo(rx_disposeBag)

入力された文字に変更がある毎に Actionを実行

Page 76: Flux with RxSwift

Flux with RxSwift

Page 77: Flux with RxSwift

一周しました!

Flux with RxSwift

Page 78: Flux with RxSwift

Proposals

Page 79: Flux with RxSwift

Proposals

• Dispatcherは一つにしたい• Storeは Singletonでなく、適切なライフサイクルにしたい• Storeを read-onlyにしたい

Page 80: Flux with RxSwift

Conclusion

Page 81: Flux with RxSwift

ConclusionPros

• 複数の ViewController, Viewを使って複雑な状態管理をするアプリケーションに向いている

• 開発者の実装が統一されやすい• View間の依存関係が減る

Page 82: Flux with RxSwift

ConclusionCons

• Singletonで集中管理なので、好き嫌いあるかも

• 慣れるまで少しかかる• 実装が冗長に感じる… ?

Page 83: Flux with RxSwift

Flux は枠組みなので、より良くするためにチームで Trial&Error してます

Flux with RxSwift

Page 84: Flux with RxSwift

Thank you

参考資料https://facebook.github.io/flux/https://github.com/facebook/fluxhttps://github.com/thoughtbot/Deltahttps://github.com/yonekawa/SwiftFluxhttps://speakerdeck.com/ogaclejapan/flux-de-relax

Page 85: Flux with RxSwift

https://github.com/dekatotoro/FluxWithRxSwiftSample

Flux with RxSwift Sample