Slide 1

Slide 1 text

VueFlux Flux inspired state managements 2018/03/22 iOS Flux / Redux ษڧձ #iOSFluxReduxษڧձ Ryo Aoyama - @ra1028

Slide 2

Slide 2 text

Profile - Ryo Aoyama - CyberAgent, Inc / FRESH! / 2016/04~ - GitHub: @ra1028 - Twitter: @ra1028fe5

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

όϥΤςΟʹ෋ΜͩϥΠϒಈըͷ഑৴ϓϥοτϑΥʔϜ ϩάΠϯෆཁɾߴը࣭ɾ௿஗ԆͰݟ์୊

Slide 5

Slide 5 text

State managements ঢ়ଶ؅ཧ

Slide 6

Slide 6 text

MVVM

Slide 7

Slide 7 text

ෳࡶԽͷҰ్Λḷͬͨଟ༷ͳViewͷঢ়ଶΛਖ਼͘͠؅ཧ͢Δ ͨΊը໘Λߏ੒͢ΔϨΠϠΛModel-View-ViewModelʹ෼ׂ MVVMͷΞϓϩʔν

Slide 8

Slide 8 text

MVVMͷΞϓϩʔν ViewͷUser interactionʹΑΔΠϕϯτΛViewModelʹૹ৴ ViewModel͸Model͔ΒσʔλΛऔಘ͠ɺ ߋ৽ΛData-binding(ଟ͘͸FRP)ʹΑͬͯViewʹ௨஌͢Δ

Slide 9

Slide 9 text

"ঢ়ଶ؅ཧ" ؆୯͡Όͳ͍͔ʂʂ

Slide 10

Slide 10 text

ຊ౰ͩΖ͏͔ ΋͏Ұาߟ͑ΛਐΊͯΈΔ

Slide 11

Slide 11 text

MVVMͷ໰୊఺ - ੹຿աଟ ঢ়ଶͷߋ৽ɺσʔλͷऔಘɺViewʹඞཁͳ஋΁Ճ޻ɺetc... UIҎ֎ͷϩδοΫΛຆͲΛ੥͚ෛ͏ͨΊɺංେԽ͠΍͍͢

Slide 12

Slide 12 text

MVVMͷ໰୊఺ - ঢ়ଶߋ৽ͷݟ௨͠ͷѱ͞ ViewModel͸಺෦ͰಉظɾඇಉظॲཧΛߦ͏ͨΊɺ ࣮૷͕ෳࡶʹͳΔʹ൐͍ݟ௨͕͠ѱ͘ͳΔ εϨου΁ͷؾݣ͍΋ඞཁ

Slide 13

Slide 13 text

MVVMͷ໰୊఺ - ૒ํ޲σʔλϑϩʔʹΑΔෳࡶੑ FRPΛར༻ͨ͠૒ํ޲ͷσʔλϑϩʔ͸ɺ ඇৗʹ௥੻͠ʹ͍͘

Slide 14

Slide 14 text

MVVMͷ໰୊఺ - ViewҎ֎ͷঢ়ଶʹؔ͢ΔϨΠϠ͕ͳ͍ Viewͷঢ়ଶ͸ViewModelʹ෼཭Ͱ͖͕ͨɺྫ͑͹ΞϓϦશମͰ ڞ༗͢Δঢ়ଶʹؔ༩͢Δ࢓૊Έ͸ͳ͍ γϯάϧτϯͷManagerΫϥεͷग़ݱ...

Slide 15

Slide 15 text

ৗʹ໖ີʹઃܭ͞ΕͨMVVM͸໰୊ͳ͍ʂ ൓໘ 
 ঢ়ଶ؅ཧͱݴ͏ʹ͸ ͋·Γʹශऑͳ࢓૊Έ͔͠ͳ͔ͬͨ...

Slide 16

Slide 16 text

ߋʹద੾ͳঢ়ଶ؅ཧϑϩʔͱϨΠϠ෼ׂ͕๬·ΕΔ

Slide 17

Slide 17 text

୯Ұํ޲σʔλϑϩʔΛݫີͳڧ੍ ঢ়ଶมߋͷϩδοΫΛཧղɾ༧ଌՄೳʹ؅ཧ͢Δ Unidirectional Data Flow

Slide 18

Slide 18 text

Flux

Slide 19

Slide 19 text

ঢ়ଶΛ؅ཧ͢ΔϨΠϠΛStore-Dispatcher-Action-Viewʹ ෼ׂ͢Δ͜ͱͰ୯Ұํ޲σʔλϑϩʔΛ࣮ݱ͢Δ FluxͷΞϓϩʔν

Slide 20

Slide 20 text

ঢ়ଶΛมߋ͢Δࡍ͸ɺඞͣActionΛൃߦ͠Dispatcherʹૹ৴͢Δ Store͸DispatcherͷίʔϧόοΫͰͷΈࣗ਎ͷঢ়ଶΛߋ৽͢Δ View͸Storeͷঢ়ଶΛ؂ࢹ͠ɺදࣔΛߋ৽ͤ͞Δ FluxͷΞϓϩʔν

Slide 21

Slide 21 text

͜Ε͸͍͍΋ͷͩ

Slide 22

Slide 22 text

͔͠͠ݒ೦΋͋Δ

Slide 23

Slide 23 text

Fluxͷݒ೦ - Storeͷ࣮૷͕։ൃऀʹҕͶΒΕ͍ͯΔ ෼ׂ୯Ґʹܾ·Γ͕ͳ͍ Dispatcherͷߪಡ։࢝ɾղআͷ࣮૷ɺঢ়ଶͷߋ৽ɺView༻ͷ஋ ΁ͷม׵ͳͲ࣮૷͢΂͖੹຿͸ґવͱͯ͠ଟ͍

Slide 24

Slide 24 text

Fluxͷݒ೦ - Dispatcher͕γϯάϧτϯ FluxͰ͸Dispatcher͕ΞϓϦʹ།ҰͳͷͰɺStoreݸผʹdispatch͕Ͱ͖ͳ͍ શͯͷActionͷܕΛڐ༰͢ΔͷͰܕ৘ใ͕εϙΠϧ͞Εɺ໢ཏੑ΋ͳ͘ͳΔ

Slide 25

Slide 25 text

ͦ͏͍͑͹Redux͸Ͳ͏ͳͷʁ

Slide 26

Slide 26 text

Reduxͷݒ೦ Store͕γϯάϧτϯͰState͕πϦʔঢ়ʹूத؅ཧ͞Ε͍ͯΔ iOSͷ৔߹͸ෳ਺ͷಉ͡ը໘͕ಉ࣌ʹଘࡏ͢Δ(ChildViewController΋ؚΉ)έʔε ͕ଟ͍ͨΊɺݸผͷߋ৽͕೉͍͠

Slide 27

Slide 27 text

Reduxͷݒ೦ ReduxͰѻ͏State͸immutableͰ͋ΓɺભҠؔ਺΋෭࡞༻ͷͳ͍७ਮؔ਺ ར఺ͱҾ͖׵͑ʹɺશͯͷStateͷͲΕ͕ߋ৽͞Εͯ΋௨஌͞ΕΔ WebͰ͸VDOMΛ࢖ͬͨࠩ෼ߋ৽ʹΑͬͯޮ཰తͳ൓ө͕Ͱ͖Δ͕ɺiOSͷ৔߹͸ UIKitͷ࠶࣮૷͕ඞཁʹͳΔ...

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

iOSͷจԽʹ࠷దԽͨ͠FluxΛߟ͑Δ

Slide 30

Slide 30 text

VueFlux / VueFluxReactive https://github.com/ra1028/VueFlux Unidirectional State Management Architecture for Swift

Slide 31

Slide 31 text

ঢ়ଶΛ؅ཧ͢ΔϨΠϠΛStore-Actions-Mutations-State-Computed-View ʹ෼ׂ͢Δ͜ͱͰ୯Ұํ޲σʔλϑϩʔΛ࣮ݱ͢Δ VueFluxͷΞϓϩʔν

Slide 32

Slide 32 text

View͸஋ͷߋ৽ΛData bindingͰ൓ө͢Δ ReactiveSwift, RxSwiftΛಋೖΛඞਢʹ͠ͳ͍Α͏ʹ؆қతͳ Reactive SystemͰ͋ΔVueFluxReactiveΛ࢖͏ͷ΋Մೳ VueFluxͷΞϓϩʔν

Slide 33

Slide 33 text

σʔλͷऔಘͳͲΛߦ͍ɺͦͷ݁ՌΛActionͱͯ͠dispatch͢Δ Action͸಺෦తʹDispatcherΛհͯ͠Mutationsʹcommit͞ΕΔ Actions

Slide 34

Slide 34 text

Actions enum UserAction { case searched(result: Result<[User], Session.Error>) } extension Actions where State == UserState { func search(query: String) -> Disposable { let request = SearchUser(query: query, page: 1, perPage: 100) return Session.send(request: request).observe { result in self.dispatch(action: .searched(result: result)) } } }

Slide 35

Slide 35 text

Actions enum UserAction { case searched(result: Result<[User], Session.Error>) } extension Actions where State == UserState { func search(query: String) -> Disposable { let request = SearchUser(query: query, page: 1, perPage: 100) return Session.send(request: request).observe { result in self.dispatch(action: .searched(result: result)) } } } ঢ়ଶΛભҠ͢ΔͨΊͷActionΛఆٛ͢Δ ܕ͸GenericsͰܾఆ͞ΕΔͷͰͳΜͰ΋OK

Slide 36

Slide 36 text

enum UserAction { case searched(result: Result<[User], Session.Error>) } extension Actions where State == UserState { func search(query: String) -> Disposable { let request = SearchUser(query: query, page: 1, perPage: 100) return Session.send(request: request).observe { result in self.dispatch(action: .searched(result: result)) } } } Actions APIϦΫΤετͷ݁Ռ౳ʹԠͯ͡ActionΛૹ৴ ݕࡧ݁ՌΛdispatch

Slide 37

Slide 37 text

ൃߦ͞ΕͨActionΛγʔέϯγϟϧʹҰఆͷContext(εϨου౳)ͰMutaionsʹcommit͢Δ VueFlux಺෦ͰӅṭ͞Ε͍ͯΔͷͰ࣮૷ऀ͸ҙࣝ͠ͳͯ͘ྑ͍ Dispatcher

Slide 38

Slide 38 text

౉͞ΕͨStateͱActionʹԠͯ͡ࢀরܕͰ͋ΔStateʹ௚઀มߋΛՃ͑Δ VueFluxͰStateͷมߋΛ།Ұڐ͞ΕΔϨΠϠ Mutations

Slide 39

Slide 39 text

struct UserMutations: Mutations { func commit(action: UserAction, state: UserState) { switch action { case .searched(result: .success(let users)): state.cellModels.value = users.map(UserCellModel.init) case .searched(result: .failure): state.cellModels.value.removeAll() } } } Mutations

Slide 40

Slide 40 text

struct UserMutations: Mutations { func commit(action: UserAction, state: UserState) { switch action { case .searched(result: .success(let users)): state.cellModels.value = users.map(UserCellModel.init) case .searched(result: .failure): state.cellModels.value.removeAll() } } } Mutations ActionʹPayloadͱͯ͠ఴ෇͞Εͨ஋Λ࢖ͬͯStateΛߋ৽͢Δ

Slide 41

Slide 41 text

StoreʹΑͬͯ؅ཧ͞ΕΔ࣮ࡍͷঢ়ଶ ࣗ਎ͷϥΠϑλΠϜʹ͍ͭͯ஌Δඞཁ͸ͳ͍ State

Slide 42

Slide 42 text

State final class UserState: State { typealias Action = UserAction typealias Mutations = UserMutations fileprivate let cellModels = Variable<[UserCellModel]>([]) }

Slide 43

Slide 43 text

State final class UserState: State { typealias Action = UserAction typealias Mutations = UserMutations fileprivate let cellModels = Variable<[UserCellModel]>([]) } State͕ActionͱMutationsΛassociatedtypeʹ࣋ͪɺܕΛଋറ͢Δ

Slide 44

Slide 44 text

State final class UserState: State { typealias Action = UserAction typealias Mutations = UserMutations fileprivate let cellModels = Variable<[UserCellModel]>([]) } MutationsΛಉ͡ϑΝΠϧ಺ʹهड़͠ɺfileprivateʹ͢Δ͜ͱͰ MutationsҎ֎͔ΒͷมߋΛڐՄ͠ͳ͍

Slide 45

Slide 45 text

Stateͷmutableͳ஋ΛImmutableͳView༻ͷ஋ʹม׵ͯ͠ެ։͢Δ Computed

Slide 46

Slide 46 text

Computed extension Computed where State == UserState { var cellModels: Constant<[UserCellModel]> { return state.cellModels.constant } var countText: Signal { return state.cellModels.signal.map { String($0.count) } } }

Slide 47

Slide 47 text

extension Computed where State == UserState { var cellModels: Constant<[UserCellModel]> { return state.cellModels.constant } var countText: Signal { return state.cellModels.signal.map { String($0.count) } } } Computed ஋Λ௚઀ߋ৽Ͱ͖ͳ͍Α͏ʹɺimmutableʹม׵͢Δ

Slide 48

Slide 48 text

extension Computed where State == UserState { var cellModels: Constant<[UserCellModel]> { return state.cellModels.constant } var countText: Signal { return state.cellModels.signal.map { String($0.count) } } } Computed View͕࢖͍΍͍͢஋ʹม׵͢Δ

Slide 49

Slide 49 text

Dispatcher͔ΒActionΛड͚औͬͯMutationsʹ౉͢ StateΛ؅ཧ͠ɺϥΠϑλΠϜΛද͢ίϯςφ Store

Slide 50

Slide 50 text

Store private let store = Store(state: .init(), mutations: .init(), executor: .queue(.global())) store.computed.countText.bind(to: countLabel, \.text) store.computed.cellModels.signal.bind(to: tableView, on: .queue(.main)) { tableView, _ in tableView.reloadData() } store.actions.search(query: "GitHub")

Slide 51

Slide 51 text

Store private let store = Store(state: .init(), mutations: .init(), executor: .queue(.global())) store.computed.countText.bind(to: countLabel, \.text) store.computed.cellModels.signal.bind(to: tableView, on: .queue(.main)) { tableView, _ in tableView.reloadData() } store.actions.search(query: "GitHub") ॳظԽ࣌ʹStateͱMutationsΛ౉͢ ͜ͷͱ͖౉͢Executor͕MutationsͰͷঢ়ଶมߋͷContextΛܾఆ͢Δ

Slide 52

Slide 52 text

Store private let store = Store(state: .init(), mutations: .init(), executor: .queue(.global())) store.computed.countText.bind(to: countLabel, \.text) store.computed.cellModels.signal.bind(to: tableView, on: .queue(.main)) { tableView, _ in tableView.reloadData() } store.actions.search(query: "GitHub") Computedͷ஋ΛViewʹbind͓ͯ͘͠

Slide 53

Slide 53 text

Store private let store = Store(state: .init(), mutations: .init(), executor: .queue(.global())) store.computed.countText.bind(to: countLabel, \.text) store.computed.cellModels.signal.bind(to: tableView, on: .queue(.main)) { tableView, _ in tableView.reloadData() } store.actions.search(query: "GitHub") StoreͷΠϯελϯε͕࣋ͭActionsͷؔ਺Λ࣮ߦ͢Δ͜ͱͰɺ ݸผʹߋ৽Ͱ͖Δ

Slide 54

Slide 54 text

Store private let store = Store(state: .init(), mutations: .init(), executor: .queue(.global())) store.computed.countText.bind(to: countLabel, \.text) store.computed.cellModels.signal.bind(to: tableView, on: .queue(.main)) { tableView, _ in tableView.reloadData() } Store.actions.search(query: "GitHub") staticͳActions͔Βؔ਺Λ࣮ߦ͢ΔͱΞϓϦͷͲ͔͜ΒͰ΋ StoreܕͷશͯͷΠϯελϯεʹActionΛbroadcastͰ͖Δ

Slide 55

Slide 55 text

Example App https://github.com/ra1028/VueFluxExample-GitHub ra1028/VueFluxExample-GitHub

Slide 56

Slide 56 text

VueFlux • ঢ়ଶετΞͷ໾ׂΛߋʹࡉ෼Խͯ͠ݟ௨͠ͷྑ͍ߏ଄ • ViewModelͷΑ͏ͳը໘ͷঢ়ଶ͔ΒɺΞϓϦશମͷঢ়ଶ·Ͱ StoreͷϥΠϑλΠϜͰදݱ͢Δ • MVVMͱFluxͷσʔλϑϩʔΛ૊Έ߹ΘͤͯɺiOSͰͷ࣮૷ʹ าΈدͬͨFlux

Slide 57

Slide 57 text

No content

Slide 58

Slide 58 text

No content

Slide 59

Slide 59 text

·ͱΊ • MVVM͸σʔλϑϩʔͱɺϨΠϠ෼ׂͷ૒ํʹ໰୊͕͋Δ • Fluxͷ࢓૊Έ͸σʔλϑϩʔ͕༏Ε͍ͯΔ͕ɺݒ೦΋͋Δ • Redux͸ ͔͠͠iOSʹ͸߹Θͳ͍Ұ໘΋ • VueFlux͸ֵ৽త͡Όͳ͘ɺطଘͷΞʔΩςΫνϟͷऑ఺ Λิͬͨ࢓૊ΈΛఏڙ͢Δ

Slide 60

Slide 60 text

Thanks