Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Flux with RxSwift
AbemaTV Developer Conference 2016
Yuji Hato
About me
Yuji Hato
CyberAgent, Inc. / AbemaTV, Inc.
dekatotoro
@dekatotoro
Contributed services
What is Flux?
What is Flux?
https://facebook.github.io/flux/docs/overview.html
“Data in a Flux application flows in a single
direction”
What is Flux?
https://github.com/facebook/flux
What is Flux?
https://github.com/facebook/flux
What is Flux?
https://github.com/facebook/flux
Observer パターン !
Why Flux?
Why Flux?
昨今のアプリ開発は複雑化
の一途を辿っており状態管理
が大変
Why Flux?
AbemaTVの状態管理:
Cast
オンデマンド
課金
CM
Filler
画質
視聴予約
etc…
番組 Fresh
コメント
視聴数
FullScreen
Feed
Why Flux?
つらい…
Why Flux?
UI操作・時間に伴う複雑な
状態遷移を分かりやすくしたい
Why Flux?
View間の依存関係を減らしたい
Why Flux?
Fluxが向いてそう…?
Flux with RxSwift
Flux with RxSwift
Flux with RxSwift
Event Streamで繋いじゃおうぜ
Flux with RxSwift
Flux with RxSwift
Eventは2種類のHot
Observableを使う
Flux with RxSwift
PublishSubject
一切キャッシュしないSubject
Flux with RxSwift
Variable
直近の値を1つだけキャッシュするSubject
※BehaviorSubjectのwrapper
Dispatcher
Dispatcher
Dispatcher
Dispatcherのフロー
Dispatcher
Actionがdispatchする
Dispatcher
dispatchされたらStoreへ通知
Dispatcher
class DispatchSubject<Element>: ObservableType, ObserverType {
typealias E = Element
fileprivate let subject = ...
Dispatcher
ObservableType, ObserverType
fileprivate let subject = PublishSubject<E>()
func on(_ event: Event<E>) {
subject...
Dispatcher
func dispatch(_ value: E) {
on(.next(value))
}
dispatchするとeventを発行します
Dispatcher
class SomeDispatcher {
static let shared = SomeDispatcher()
let loading = DispatchSubject<Bool>()
let error = D...
Dispatcher
let loading = DispatchSubject<Bool>()
let error = DispatchSubject<Error>()
let someModel = DispatchSubject<Some...
Dispatcher
func someAction(value: Bool) {
…
dispatcher.loading.dispatch(value)
…
}
Actionがdispatch
Action
Action
Action
Actionのフロー
Action
ViewからActionを実行
Action
必要なデータを取得する
Action
UI, Web, DB, Devicesを外部IFとして捉える
https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html
Action
データの取得後Dispatcherへ流す
Action
func someAction(query: String) {
dispatcher.loading.dispatch(true)
API.getSome(with: query)
.do(onError: { [unowned...
Action
dispatcher.loading.dispatch(true)
API.getSome(with: query)
ローディングしてAPI実行
Action
.do(onError: { [unowned self] error in
self.dispatcher.error.dispatch(error)
self.dispatcher.loading.dispatch(false...
Action
.subscribe(onNext: { [unowned self] response in
let someModel = SomeModel.make(from: response)
self.dispatcher.some...
Action
.do(onCompleted: { [unowned self] error in
self.dispatcher.loading.dispatch(false)
})
実行が完了した時
Store
Store
Store
Storeのフロー
Store
DispatcherのeventをObserveする
Store
Dispatcherのイベントが流れてくる
Store
Dispatcherのイベントを検知したらデータを更新
Store
データを更新したらViewへ通知
Flux: Store
class Store {
let disposeBag = DisposeBag()
func bind<O, E>(_ observable: O, _ param: Variable<E>) where O: Ob...
Flux: Store
func bind<O, E>(_ observable: O, _ param: Variable<E>) where O: ObservableType, E == O.E
{
observable.bindTo(p...
Flux: Store
func bind<O, E>(_ observable: O, _ param: PublishSubject<E>) where O: ObservableType, E
== O.E {
observable.bi...
Flux: Store
class SomeStore: Store {
static let shared = SomeStore()
let loading = Variable<Bool>(false)
let error = Publi...
Flux: Store
static let shared = SomeStore()
StoreクラスはSingleton
Flux: Store
let loading = Variable<Bool>(false)
let error = PublishSubject<Error>()
let someModel = Variable<SomeModel>(So...
Flux: Store
init(dispatcher: SomeDispatcher = .shared) {
super.init()
bind(dispatcher.loading, loading)
bind(dispatcher.er...
View
View
View
Viewのフロー
View
StoreとUIのeventをObserveする
View
Storeのイベントが流れてくる
View
Storeのイベントを検知したらViewを更新
View
UIのeventをトリガーにActionを実行
View
store.loading.asObservable()
.distinctUntilChanged()
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [unowned...
View
store.loading.asObservable()
.distinctUntilChanged()
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [unowned...
View
self.loadingView.hidden(!loading)
loadingViewの表示/非表示
View
store.error
.subscribe(onNext: { error in
})
.addDisposableTo(rx_disposeBag)
Storeのerrorをobserve
View
ErrorAction.show(.apiError(error))
ErrorActionを実行
View
store.someModel.asObservable()
.map { $0.dataSource }
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [unowne...
View
self.dataSource.value = dataSource
self.tableView?.reloadData()
tableViewをreload
View
searchBar.rx.text.asDriver()
.throttle(0.3)
.distinctUntilChanged()
.drive(onNext: { query in
SomeAction.someAction(q...
View
searchBar.rx.text.asDriver()
.throttle(0.3)
.distinctUntilChanged()
.drive(onNext: { query in
})
.addDisposableTo(rx_...
View
SomeAction.someAction(query: query)
})
入力された文字に変更がある毎にActionを実行
Flux with RxSwift
一周しました!
Flux with RxSwift
Proposals
Proposals
• Dispatcherは一つにしたい
• StoreはSingletonでなく、適切なライフサイク
ルにしたい
• Storeをread-onlyにしたい
Conclusion
Conclusion
Pros
• 複数のViewController, Viewを使って複雑な状態
管理をするアプリケーションに向いている
• 開発者の実装が統一されやすい
• View間の依存関係が減る
Conclusion
Cons
• Singletonで集中管理なので、好き嫌いあるかも
• 慣れるまで少しかかる
• 実装が冗長に感じる…?
Fluxは枠組みなので、より良くするために
チームでTrial&Errorしてます
Flux with RxSwift
Thank you
参考資料
https://facebook.github.io/flux/
https://github.com/facebook/flux
https://github.com/thoughtbot/Delta
https...
https://github.com/dekatotoro/FluxWithRxSwiftSample
Flux with RxSwift Sample
Upcoming SlideShare
Loading in …5
×

Flux with RxSwift

4,567 views

Published on

AbemaTV Developer Conference 2016

Published in: Software
  • Be the first to comment

Flux with RxSwift

  1. 1. Flux with RxSwift AbemaTV Developer Conference 2016 Yuji Hato
  2. 2. About me Yuji Hato CyberAgent, Inc. / AbemaTV, Inc. dekatotoro @dekatotoro Contributed services
  3. 3. What is Flux?
  4. 4. What is Flux? https://facebook.github.io/flux/docs/overview.html “Data in a Flux application flows in a single direction”
  5. 5. What is Flux? https://github.com/facebook/flux
  6. 6. What is Flux? https://github.com/facebook/flux
  7. 7. What is Flux? https://github.com/facebook/flux Observer パターン !
  8. 8. Why Flux?
  9. 9. Why Flux? 昨今のアプリ開発は複雑化 の一途を辿っており状態管理 が大変
  10. 10. Why Flux? AbemaTVの状態管理: Cast オンデマンド 課金 CM Filler 画質 視聴予約 etc… 番組 Fresh コメント 視聴数 FullScreen Feed
  11. 11. Why Flux? つらい…
  12. 12. Why Flux? UI操作・時間に伴う複雑な 状態遷移を分かりやすくしたい
  13. 13. Why Flux? View間の依存関係を減らしたい
  14. 14. Why Flux? Fluxが向いてそう…?
  15. 15. Flux with RxSwift
  16. 16. Flux with RxSwift
  17. 17. Flux with RxSwift Event Streamで繋いじゃおうぜ
  18. 18. Flux with RxSwift
  19. 19. Flux with RxSwift Eventは2種類のHot Observableを使う
  20. 20. Flux with RxSwift PublishSubject 一切キャッシュしないSubject
  21. 21. Flux with RxSwift Variable 直近の値を1つだけキャッシュするSubject ※BehaviorSubjectのwrapper
  22. 22. Dispatcher
  23. 23. Dispatcher
  24. 24. Dispatcher Dispatcherのフロー
  25. 25. Dispatcher Actionがdispatchする
  26. 26. Dispatcher dispatchされたらStoreへ通知
  27. 27. 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
  28. 28. Dispatcher ObservableType, ObserverType fileprivate let subject = PublishSubject<E>() 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
  29. 29. Dispatcher func dispatch(_ value: E) { on(.next(value)) } dispatchするとeventを発行します
  30. 30. Dispatcher class SomeDispatcher { static let shared = SomeDispatcher() let loading = DispatchSubject<Bool>() let error = DispatchSubject<Error>() let someModel = DispatchSubject<SomeModel>() … } Dispatcherクラス
  31. 31. Dispatcher let loading = DispatchSubject<Bool>() let error = DispatchSubject<Error>() let someModel = DispatchSubject<SomeModel>() ActionTypeの代わりにDispatchSubjectを複数用意 ※Dispatcherクラスも用途ごとに分けてます
  32. 32. Dispatcher func someAction(value: Bool) { … dispatcher.loading.dispatch(value) … } Actionがdispatch
  33. 33. Action
  34. 34. Action
  35. 35. Action Actionのフロー
  36. 36. Action ViewからActionを実行
  37. 37. Action 必要なデータを取得する
  38. 38. Action UI, Web, DB, Devicesを外部IFとして捉える https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html
  39. 39. Action データの取得後Dispatcherへ流す
  40. 40. 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を実行する例
  41. 41. Action dispatcher.loading.dispatch(true) API.getSome(with: query) ローディングしてAPI実行
  42. 42. Action .do(onError: { [unowned self] error in self.dispatcher.error.dispatch(error) self.dispatcher.loading.dispatch(false) }) エラーの時
  43. 43. Action .subscribe(onNext: { [unowned self] response in let someModel = SomeModel.make(from: response) self.dispatcher.someModel.dispatch(someModel) }) レスポンスが返ってきた時
  44. 44. Action .do(onCompleted: { [unowned self] error in self.dispatcher.loading.dispatch(false) }) 実行が完了した時
  45. 45. Store
  46. 46. Store
  47. 47. Store Storeのフロー
  48. 48. Store DispatcherのeventをObserveする
  49. 49. Store Dispatcherのイベントが流れてくる
  50. 50. Store Dispatcherのイベントを検知したらデータを更新
  51. 51. Store データを更新したらViewへ通知
  52. 52. 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親クラス
  53. 53. Flux: Store func bind<O, E>(_ observable: O, _ param: Variable<E>) where O: ObservableType, E == O.E { observable.bindTo(param).addDisposableTo(disposeBag) } Variable用のbind
  54. 54. Flux: Store func bind<O, E>(_ observable: O, _ param: PublishSubject<E>) where O: ObservableType, E == O.E { observable.bindTo(param).addDisposableTo(disposeBag) } PublishSubject用のbind
  55. 55. 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クラス
  56. 56. Flux: Store static let shared = SomeStore() StoreクラスはSingleton
  57. 57. Flux: Store let loading = Variable<Bool>(false) let error = PublishSubject<Error>() let someModel = Variable<SomeModel>(SomeModel()) StoreのpropertyはVariableとPublishSubject
  58. 58. Flux: Store init(dispatcher: SomeDispatcher = .shared) { super.init() bind(dispatcher.loading, loading) bind(dispatcher.error, error) bind(dispatcher.someModel, someModel) } initでdispatcherとbind
  59. 59. View
  60. 60. View
  61. 61. View Viewのフロー
  62. 62. View StoreとUIのeventをObserveする
  63. 63. View Storeのイベントが流れてくる
  64. 64. View Storeのイベントを検知したらViewを更新
  65. 65. View UIのeventをトリガーにActionを実行
  66. 66. 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
  67. 67. View store.loading.asObservable() .distinctUntilChanged() .observeOn(MainScheduler.instance) .subscribe(onNext: { [unowned self] loading in }) .addDisposableTo(rx_disposeBag) Storeのloadingをobserve
  68. 68. View self.loadingView.hidden(!loading) loadingViewの表示/非表示
  69. 69. View store.error .subscribe(onNext: { error in }) .addDisposableTo(rx_disposeBag) Storeのerrorをobserve
  70. 70. View ErrorAction.show(.apiError(error)) ErrorActionを実行
  71. 71. View store.someModel.asObservable() .map { $0.dataSource } .observeOn(MainScheduler.instance) .subscribe(onNext: { [unowned self] dataSource in }) .addDisposableTo(rx_disposeBag) StoreのsomeModelをobserve
  72. 72. View self.dataSource.value = dataSource self.tableView?.reloadData() tableViewをreload
  73. 73. View searchBar.rx.text.asDriver() .throttle(0.3) .distinctUntilChanged() .drive(onNext: { query in SomeAction.someAction(query: query) }) .addDisposableTo(rx_disposeBag) UIのeventをobserve
  74. 74. View searchBar.rx.text.asDriver() .throttle(0.3) .distinctUntilChanged() .drive(onNext: { query in }) .addDisposableTo(rx_disposeBag) searchBarのtext入力をobserve
  75. 75. View SomeAction.someAction(query: query) }) 入力された文字に変更がある毎にActionを実行
  76. 76. Flux with RxSwift
  77. 77. 一周しました! Flux with RxSwift
  78. 78. Proposals
  79. 79. Proposals • Dispatcherは一つにしたい • StoreはSingletonでなく、適切なライフサイク ルにしたい • Storeをread-onlyにしたい
  80. 80. Conclusion
  81. 81. Conclusion Pros • 複数のViewController, Viewを使って複雑な状態 管理をするアプリケーションに向いている • 開発者の実装が統一されやすい • View間の依存関係が減る
  82. 82. Conclusion Cons • Singletonで集中管理なので、好き嫌いあるかも • 慣れるまで少しかかる • 実装が冗長に感じる…?
  83. 83. Fluxは枠組みなので、より良くするために チームでTrial&Errorしてます Flux with RxSwift
  84. 84. Thank you 参考資料 https://facebook.github.io/flux/ https://github.com/facebook/flux https://github.com/thoughtbot/Delta https://github.com/yonekawa/SwiftFlux https://speakerdeck.com/ogaclejapan/flux-de-relax
  85. 85. https://github.com/dekatotoro/FluxWithRxSwiftSample Flux with RxSwift Sample

×