Upgrade to Pro — share decks privately, control downloads, hide ads and more …

iOSアプリ開発に MVVM+Redux パターンを使い始めた話/ios-mvvm-redux-pattern

iOSアプリ開発に MVVM+Redux パターンを使い始めた話/ios-mvvm-redux-pattern

Swift愛好会 vol36で行った談義のスライドです。
最近iOSアプリをMVVM+Reduxパターンで開発し始めたので、今のところ良いと思っていることをまとめました。

Swift愛好会 vol36
https://love-swift.connpass.com/event/105183/

Takehiro Kaneko

November 09, 2018
Tweet

More Decks by Takehiro Kaneko

Other Decks in Programming

Transcript

  1. ReSwift ެࣜReferenceΑΓ • ReduxϥΠΫͳ୯ํ޲σʔλϑϩʔΞʔΩςΫνϟͷ࣮૷ • ΞϓϦΛ3ͭͷؔ৺ࣄʹ෼཭͢Δ • State: ΞϓϦશମͷঢ়ଶΛҰͭͷσʔλߏ଄Ͱදݱ •

    Views: ݱࡏͷΞϓϦͷঢ়ଶΛࢹ֮Խ͢Δ • State Changes: ঢ়ଶมߋͷํ๏Λݫ੍͘͠ݶ http://reswift.github.io/ReSwift/master/index.html
  2. struct AppState: StateType { var postListState = PostListState() } •

    ΞϓϦͷঢ়ଶΛstructͰදݱ͢Δ • ը໘୯ҐͳͲͰঢ়ଶΛ֊૚Խ͍ͯ͘͠
  3. let appStore = Store( reducer: appReducer, state: AppState(), middleware: [])

    @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { • ReSwiftͷ࢓૊ΈΛಈ͔ͨ͢Ίɺ࠷ॳʹStoreΛηοτΞοϓ͢Δ • ৔ॴ͸Ͳ͜Ͱ΋ྑ͍͕ɺੜ੒ͨ͠StoreΠϯελϯεΛอ࣋ͯ͠ ͓͘
  4. class PostListViewModel: StoreSubscriber { init(store: Store<AppState>) { self.store = store

    viewWillAppearStream .subscribe(onNext: { [unowned self] in self.store.dispatch(PostListState.listenPostsActionCreator) self.store.subscribe(self) { subcription in subcription.select { state in state.postListState } } }) .disposed(by: disposeBag) } func newState(state: PostListState) { postsStream.accept(state.posts) } } ಡॻϝϞσʔλͷऔಘΞ ΫγϣϯΛσΟεύον • ViewModelͰঢ়ଶมߋ௨஌ͷड৴͓ΑͼActionͷσΟεύον Λߦ͏Α͏ʹ͍ͯ͠Δ ঢ়ଶมߋ௨஌Λड͚औΓɺ σʔλόΠϯσΟϯάΛ௨ͯ͡ 7JFX$POUSPMMFSʹ௨஌
  5. class PostListViewController: UIViewController { func bind() { rx.sentMessage(#selector(viewWillAppear(_:))) .map {

    _ in } .bind(to: viewModel.viewWillAppear) .disposed(by: disposeBag) viewModel.posts .drive(tableView.rx.items( cellIdentifier: R.nib.postListCell.name, cellType: PostListCell.self) ) { (_, element, cell) in cell.configure(post: element) } .disposed(by: disposeBag) } } • ViewController͸ViewModelͱͷσʔλόΠϯσΟϯάͱɺ σʔλදࣔ΍ը໘ભҠ౳ͷViewػೳʹప͢Δ • ঢ়ଶʹґଘͨ͠ϩδοΫ͸ۃྗ࣋ͨͤͳ͍
  6. class PostListViewModel: StoreSubscriber { init(store: Store<AppState>) { self.store = store

    viewWillAppearStream .subscribe(onNext: { [unowned self] in self.store.dispatch(PostListState.listenPostsActionCreator) self.store.subscribe(self) { subcription in subcription.select { state in state.postListState } } }) .disposed(by: disposeBag) } func newState(state: PostListState) { postsStream.accept(state.posts) } } ͜͜Ͱ͸"DUJPO$SFBUPS ΛσΟεύον • viewWillAppearΠϕϯτΛτϦΨʔʹɺAction͕σΟεύον ͞ΕΔ
  7. extension PostListState { static func listenPostsActionCreator(state: AppState, store: Store<AppState>) ->

    Action? { let listener = db.collection("posts").addSnapshotListener { (snapshop, error) in guard let documents = snapshop?.documents else { return } let posts = documents.compactMap { Post(dictionary: $0.data()) } store.dispatch(Action.updatePosts(posts: posts)) } return Action.listenPosts(listener: listener) } } ͜ͷ෦෼ • ActionCreatorͰσʔλऔಘΛߦ͍ɺऔಘͨ͠σʔλͰAction Λੜ੒ͯ͠σΟεύον͢Δ
  8. extension PostListState { static func reducer(action: ReSwift.Action, state: PostListState?) ->

    PostListState { var state = state ?? PostListState() if let action = action as? Action { switch action { case let .updatePosts(posts): state.posts = posts } return state } } ঢ়ଶΛมߋ͢Δ • Reducer͕ActionΛड͚औΓɺঢ়ଶΛมߋ͢Δ • ৽͍͠ঢ়ଶ͸ViewModelΛ௨ͯ͡Viewʹ௨஌͞Εɺը໘ʹσʔ λ͕දࣔ͞ΕΔ
  9. class PostListViewController: UIViewController { func bind() { tableView.rx.itemSelected.asDriver() .map {

    $0.row } .drive(onNext: { [unowned self] row in let vc = self.resolve(PostViewController.self)! // DIίϯςφ͔Βੜ੒ vc.post = self.posts[row] // vc.state1 = ... // vc.state2 = ... self.present(vc) }) .disposed(by: disposeBag) } } 7JFX$POUSPMMFSʹ༷ʑͳ ঢ়ଶΛ౉͍ͯ͠Δ • Ұཡ͔Βߦ͕બ୒͞ΕͨΒɺৄࡉը໘ͷViewControllerΛੜ੒ ͠ɺબ୒͞Εͨߦͷσʔλ΍ԿΒ͔ͷঢ়ଶσʔλΛ౉ͯ͠ॳظ Խ͢Δඞཁ͕͋Δ
  10. class PostListViewController: UIViewController { func bind() { tableView.rx.itemSelected.asDriver() .map {

    $0.row } .drive(viewModel.itemSelected) .disposed(by: disposeBag) tableView.rx.itemSelected.asDriver() .drive(onNext: { [unowned self] in let vc = self.resolve(PostViewController.self)! self.present(vc, animated: true) }) .disposed(by: disposeBag) } } 7JFX$POUSPMMFSʹ ͋Ε͜Ε౉͞ͳͯ͘ྑ͍ • ߦ͕બ୒͞Εͨ͜ͱΛViewModelʹ௨஌͢Δ͜ͱͰɺৄࡉը໘ ༻ͷঢ়ଶ͕มߋ͞ΕΔ • ͋ͱ͸Կ΋ߟ͑ͣৄࡉը໘Λදࣔͯ͋͛͠Ε͹ɺৄࡉը໘ଆ͸ উखʹ࠷৽ͷঢ়ଶΛ࢖ͬͯදࣔΛͯ͘͠ΕΔ