Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Speaker Deck
PRO
Sign in
Sign up
for free
Flux_with_RxSwift.pdf
Yuji Hato
October 15, 2016
Technology
0
99
Flux_with_RxSwift.pdf
Yuji Hato
October 15, 2016
Tweet
Share
More Decks by Yuji Hato
See All by Yuji Hato
dekatotoro
5
650
dekatotoro
6
2.6k
dekatotoro
0
90
dekatotoro
0
130
dekatotoro
0
70
dekatotoro
0
72
dekatotoro
0
19
dekatotoro
0
15
dekatotoro
0
23
Other Decks in Technology
See All in Technology
am7cinnamon
2
2.7k
ymas0315
0
160
shomaekawa
3
1.2k
ocise
0
140
yosuke_matsuura
PRO
0
3.4k
yosuke_furukawa
PRO
42
14k
ray_30cm_ns
0
280
torisoup
0
290
opelab
2
290
yasuakiomokawa
0
340
bufferings
2
3.2k
masashible
0
110
Featured
See All Featured
brad_frost
156
6.4k
danielanewman
200
19k
garrettdimon
287
110k
philnash
8
490
samlambert
237
9.9k
notwaldorf
13
1.5k
jcasabona
7
520
jonyablonski
14
1.1k
philhawksworth
190
17k
chriscoyier
780
240k
mza
80
4.1k
jponch
103
5k
Transcript
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? IUUQTGBDFCPPLHJUIVCJPqVYEPDTPWFSWJFXIUNM “Data in a Flux application flows
in a single direction”
What is Flux? IUUQTHJUIVCDPNGBDFCPPLqVY
What is Flux? IUUQTHJUIVCDPNGBDFCPPLqVY
What is Flux? IUUQTHJUIVCDPNGBDFCPPLqVY Observer ύλʔϯ !
Why Flux?
Why Flux? ࡢࠓͷΞϓϦ։ൃෳࡶԽ ͷҰ్Λḷ͓ͬͯΓঢ়ଶཧ ͕େม
Why Flux? AbemaTVͷঢ়ଶཧ: Cast ΦϯσϚϯυ ՝ۚ CM Filler ը࣭ ࢹௌ༧
etc… ൪ Fresh ίϝϯτ ࢹௌ FullScreen Feed
Why Flux? ͭΒ͍…
Why Flux? UIૢ࡞ɾ࣌ؒʹ͏ෳࡶͳ ঢ়ଶભҠΛ͔Γ͍ͨ͘͢͠
Why Flux? ViewؒͷґଘؔΛݮΒ͍ͨ͠
MVVM MVC DDD Flux Clean Architecture MVP Why Flux? Flux͕͍ͯͦ͏…?
Flux with RxSwift
Flux with RxSwift Action Dispatcher User Interactions API DB ViewController
View Store Events Events
Flux with RxSwift Event StreamͰܨ͍͡Ό͓͏ͥ
Flux with RxSwift Action Dispatcher User Interactions API DB ViewController
View Store Events Events
Flux with RxSwift Dispatcher ViewController View Store Events Events Events
Event̎छྨͷHot ObservableΛ͏
Flux with RxSwift PublishSubject ҰΩϟογϡ͠ͳ͍Subject Events
Flux with RxSwift Variable ۙͷΛ͚̍ͭͩΩϟογϡ͢ΔSubject ※BehaviorSubjectͷwrapper Events
Dispatcher
Dispatcher Action Dispatcher User Interactions API DB ViewController View Store
Events Events
Dispatcher Dispatcherͷϑϩʔ Dispatcher Store Events Events Action Dispatcher Store Store
Events Events Action Action
Dispatcher Action͕dispatch͢Δ Dispatcher Store Store Events Events Action Action on
subscribe
Dispatcher dispatch͞ΕͨΒStore௨ Dispatcher Store Store Events Events Action Action on
subscribe
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
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
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Λൃߦ͠·͢
Dispatcher class SomeDispatcher { static let shared = SomeDispatcher() let
loading = DispatchSubject<Bool>() let error = DispatchSubject<Error>() let someModel = DispatchSubject<SomeModel>() … } DispatcherΫϥε
Dispatcher class SomeDispatcher { static let shared = SomeDispatcher() let
loading = DispatchSubject<Bool>() let error = DispatchSubject<Error>() let someModel = DispatchSubject<SomeModel>() … } ActionTypeͷΘΓʹDispatchSubjectΛෳ༻ҙ ※DispatcherΫϥε༻్͝ͱʹ͚ͯ·͢
Dispatcher func someAction(value: Bool) { … dispatcher.loading.dispatch(value) … } Action͕dispatch
Action
Action Action Dispatcher User Interactions API DB ViewController View Store
Events Events
Action Actionͷϑϩʔ Action Action Dispatcher User Interactions API DB ViewController
View Action Action Dispatcher API DB ViewController View Device User Interactions
Action View͔ΒActionΛ࣮ߦ Action Action Dispatcher User Interactions API DB ViewController
View Action Action Dispatcher API DB ViewController View Device User Interactions Action Action Dispatcher User Interactions API DB ViewController View Action Action Action Dispatcher API DB ViewController View Device User Interactions
Action ඞཁͳσʔλΛऔಘ͢Δ Action Action Dispatcher User Interactions API DB ViewController
View Action Action Action Dispatcher API DB ViewController View Device User Interactions
Action UI, Web, DB, DevicesΛ֎෦IFͱͯ͠ଊ͑Δ IUUQTUIMJHIUDPNCMPHVODMFCPCUIFDMFBOBSDIJUFDUVSFIUNM
Action σʔλͷऔಘޙDispatcherྲྀ͢ Action Action Dispatcher User Interactions API DB ViewController
View Action Action Action Dispatcher API DB ViewController View Device User Interactions
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Λ࣮ߦ͢Δྫ
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࣮ߦ
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) } Τϥʔͷ࣌
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) } Ϩεϙϯε͕ฦ͖ͬͯͨ࣌
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) } ࣮ߦ͕ྃͨ࣌͠
Store
Store Action Dispatcher User Interactions API DB ViewController View Store
Events Events
Store Storeͷϑϩʔ Dispatcher Store Store ViewController View Events Events Events
Store DispatcherͷeventΛObserve͢Δ Dispatcher Store Store ViewController View Events Events Events
subscribe on on subscribe
Store DispatcherͷΠϕϯτ͕ྲྀΕͯ͘Δ Store Store ViewController View Events Events Events subscribe
on on subscribe Dispatcher
Store DispatcherͷΠϕϯτΛݕͨ͠ΒσʔλΛߋ৽ Store Store ViewController View Events Events Events subscribe
on on subscribe Dispatcher
Store σʔλΛߋ৽ͨ͠ΒView௨ Store Store ViewController View Events Events Events subscribe
subscribe Dispatcher on on
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Ϋϥε
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
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
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Ϋϥε
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
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ͷpropertyVariableͱPublishSubject
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
View
View Action Dispatcher User Interactions API DB ViewController View Store
Events Events
View Viewͷϑϩʔ Dispatcher Events Store Store ViewController View Events Action
Action Action User Interactions ViewController View Action
View StoreͱUIͷeventΛObserve͢Δ Dispatcher Events Store Store ViewController View Events subscribe
Action Action User Interactions ViewController View Action
View StoreͷΠϕϯτ͕ྲྀΕͯ͘Δ Dispatcher Events Store Store ViewController View Events on
Action Action User Interactions ViewController View Action
View StoreͷΠϕϯτΛݕͨ͠ΒViewΛߋ৽ Dispatcher Events Store Store ViewController View Events on
Action Action User Interactions ViewController View Action
View UIͷeventΛτϦΨʔʹActionΛ࣮ߦ Dispatcher Events Store Store ViewController View Events Action
Action User Interactions ViewController View Action
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
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
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ͷදࣔ/ඇදࣔ
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
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Λ࣮ߦ
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
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
View searchBar.rx.text.asDriver() .throttle(0.3) .distinctUntilChanged() .drive(onNext: { query in SomeAction.someAction(query: query)
}) .addDisposableTo(rx_disposeBag) UIͷeventΛobserve
View searchBar.rx.text.asDriver() .throttle(0.3) .distinctUntilChanged() .drive(onNext: { query in SomeAction.someAction(query: query)
}) .addDisposableTo(rx_disposeBag) searchBarͷtextೖྗΛobserve
View searchBar.rx.text.asDriver() .throttle(0.3) .distinctUntilChanged() .drive(onNext: { query in SomeAction.someAction(query: query)
}) .addDisposableTo(rx_disposeBag) ೖྗ͞Εͨจࣈʹมߋ͕͋ΔຖʹActionΛ࣮ߦ
Flux with RxSwift Action Dispatcher User Interactions API DB ViewController
View Store Events Events
Ұप͠·ͨ͠ʂ Flux with RxSwift
Proposals
Proposals • DispatcherҰͭʹ͍ͨ͠ • StoreSingletonͰͳ͘ɺదͳϥΠϑαΠ Ϋϧʹ͍ͨ͠ • 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://github.com/yonekawa/SwiftFlux https://speakerdeck.com/ogaclejapan/flux-de-relax
https://github.com/dekatotoro/FluxWithRxSwiftSample Flux with RxSwift Sample