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/

1da8b476058df860d83a12c496b74fff?s=128

Takehiro Kaneko

November 09, 2018
Tweet

Transcript

  1. iOSΞϓϦ։ൃʹ MVVM+Reduxύλʔϯ Λ࢖͍࢝Ίͨ࿩ SwiftѪ޷ձ vol36 2018/11/09 @takehilo_kaneko

  2. ࣗݾ঺հ ۚࢠ ༤େ @takehilo_kaneko 1೥͘Β͍ https://qiita.com/takehilo

  3. ͜ͷτʔΫͷ໨త • ࠷ۙݸਓ։ൃͷΞϓϦͰMVVM+ReduxύλʔϯΛ࢖ͬͯ։ൃ Λ࢝Ίͨ • ·ͩReduxΛษڧ࢝͠Ίͯ2िؒ͘Β͍͚ͩͲɺࠓͷஈ֊Ͱྑ ͍ͱײ͍ͯ͡Δ͜ͱΛ࿩͢

  4. ର৅ऀ • MVVM͓ΑͼRxSwiftΛ࢖ͬͨ͜ͱ͕͋Δਓ • Redux͓ΑͼReSwiftʹ͍ͭͯ͸͋·Γ஌Βͳ͍ਓ

  5. ࢓ࣄͰMVVMͰ։ൃΛ͍ͯͯ͠ɺ ͣͬͱReduxΛೖΕͯΈ͔ͨͬͨʂ

  6. MVVMͰ։ൃ͍ͯͯ͠ײ͍ͯ͡Δ՝୊

  7. ෳ਺ը໘ؒͰͷ ঢ়ଶͷڞ༗ํ๏ ঢ়ଶมԽͷ௨஌ํ๏ ʹ͍ͭͯͷϧʔϧ͕ͳ͍

  8. MVVMͷ՝୊ • ҰཡλϒͰ͓ؾʹೖΓ௥Ճˠ͓ؾʹೖΓҰཡλϒʹଈ൓ө • ֤ViewController͸ผʑͷViewModelʹґଘ͍ͯ͠Δ • ViewModelؒͰঢ়ଶมԽΛͲ͏௨஌͢Δʁ • Ұཡը໘͔Βৄࡉը໘ʹભҠ࣌ʹϞσϧ΍ঢ়ଶΛ౉͢ •

    Ϟσϧ΍ঢ়ଶΛViewControllerʹ౉͢ʁViewModelʹ౉͢ʁ • ౉͢ύϥϝʔλ͕ଟ͍ͱɺίʔυมߋ࣌ʹ౉͠๨Ε͕ग़͖ͯ ͦ͏
  9. MVVMͷ՝୊ • Ұཡը໘͔Βݕࡧ৚݅બ୒ը໘ʹભҠ͠ɺݕࡧ৚݅Λબ୒ͨ͠ ΒҰཡը໘ʹ໭ͬͯݕࡧΛ࣮ߦ • Ͳ͏΍ͬͯલͷը໘ʹঢ়ଶΛ౉͢ʁ • DelegateύλʔϯΛ࢖͏ɺҰཡը໘༻ViewModelΛݕࡧ৚݅ બ୒ը໘Ͱ΋ڞ༗͢ΔɺͳͲͷ΍Γํ͕ߟ͑ΒΕΔ

  10. ͍ͣΕͷ՝୊΋ղܾํ๏͸͋Δ

  11. ͔͠͠ɺ MVVMʹ׳Εͨϝϯόʔ͕͓Βͣɺ ઌߦͯ͠ϧʔϧΛܾΊΒΕͳ͔ͬͨͨΊɺ ը໘͝ͱʹ΍Γํ͕ҟͳͬͯ͠·ͬͨ

  12. ঢ়ଶͷѻ͍ํʹؔ͢Δڞ௨ͷϧʔϧ͕΄͍͠

  13. Redux

  14. ࠓ೔͸Reduxͱ͸Կ͔ͱ͍͏࿩͸͠ͳ͍ ʢͱ͍͏͔Ͱ͖Δ΄Ͳͷ஌ࣝ͸·ͩͳ͍ʣ

  15. ͜Ε͔Β঺հ͢Δ࣮૷Πϝʔδ͔Β งғؾΛ௫ΜͰΈ͍ͯͩ͘͞

  16. ReduxͱReSwiftʹ͍ͭͯ͸ͪ͜ΒΛࢀর ͜ΕಡΜ͚ͩͩͰ։ൃ࢝ΊΒΕ·ͨ͠ʂ https://qiita.com/susieyy/items/23d44f28c6a6915c58e2

  17. MVVM+ReduxύλʔϯΛద༻ͨ͠ ΞϓϦ࣮૷ྫΛ঺հ͢Δ

  18. ΞϓϦ֓ཁ • ಡॻϝϞΛऔΔΞϓϦ • RxSwiftͱReSwiftΛ࢖ͬͯMVVM+ReduxύλʔϯΛ࣮૷ • ʢ͜͜Ͱ͸͋Μ·Γؔ܎ͳ͍͕ʣόοΫΤϯυʹFirestoreΛ࠾ ༻͍ͯ͠Δ

  19. ReSwift

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

    Views: ݱࡏͷΞϓϦͷঢ়ଶΛࢹ֮Խ͢Δ • State Changes: ঢ়ଶมߋͷํ๏Λݫ੍͘͠ݶ http://reswift.github.io/ReSwift/master/index.html
  21. None
  22. ಡॻϝϞҰཡදࣔը໘Λྫʹ࣮૷Λݟ͍ͯ͘

  23. Stateͷ࣮૷

  24. struct AppState: StateType { var postListState = PostListState() } •

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

    @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { • ReSwiftͷ࢓૊ΈΛಈ͔ͨ͢Ίɺ࠷ॳʹStoreΛηοτΞοϓ͢Δ • ৔ॴ͸Ͳ͜Ͱ΋ྑ͍͕ɺੜ੒ͨ͠StoreΠϯελϯεΛอ࣋ͯ͠ ͓͘
  26. View + ViewModelͷ࣮૷

  27. 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ʹ௨஌
  28. 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ػೳʹప͢Δ • ঢ়ଶʹґଘͨ͠ϩδοΫ͸ۃྗ࣋ͨͤͳ͍
  29. Action + Reducerͷ࣮૷

  30. 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͕σΟεύον ͞ΕΔ
  31. 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 Λੜ੒ͯ͠σΟεύον͢Δ
  32. 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ʹ௨஌͞Εɺը໘ʹσʔ λ͕දࣔ͞ΕΔ
  33. ͜ͷ࢓૊ΈͰMVVMͷ՝୊͕ղܾͰ͖Δ͔

  34. ՝୊ͷ1ͭΛ͓͞Β͍ • Ұཡը໘͔Βৄࡉը໘ʹભҠ࣌ʹϞσϧ΍ঢ়ଶΛ౉͢ • Ϟσϧ΍ঢ়ଶΛViewControllerʹ౉͢ʁViewModelʹ౉͢ʁ • ౉͢ύϥϝʔλ͕ଟ͍ͱɺίʔυมߋ࣌ʹ౉͠๨Ε͕ग़͖ͯ ͦ͏

  35. 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Λੜ੒ ͠ɺબ୒͞Εͨߦͷσʔλ΍ԿΒ͔ͷঢ়ଶσʔλΛ౉ͯ͠ॳظ Խ͢Δඞཁ͕͋Δ
  36. 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ʹ௨஌͢Δ͜ͱͰɺৄࡉը໘ ༻ͷঢ়ଶ͕มߋ͞ΕΔ • ͋ͱ͸Կ΋ߟ͑ͣৄࡉը໘Λදࣔͯ͋͛͠Ε͹ɺৄࡉը໘ଆ͸ উखʹ࠷৽ͷঢ়ଶΛ࢖ͬͯදࣔΛͯ͘͠ΕΔ
  37. ᶃ selectPostΞΫγϣϯ ͕σΟεύον͞ΕΔ ᶄ ৄࡉը໘༻ͷঢ়ଶͰ͋Δ PostState͕มߋ͞ΕΔ ᶅ ৄࡉը໘͕࠷৽ͷ PostStateͰදࣔ͞ΕΔ

  38. ଞͷ՝୊΋εοΩϦղܾ • ҰཡλϒͰ͓ؾʹೖΓ௥Ճˠ͓ؾʹೖΓҰཡλϒʹଈ൓ө • Ұཡը໘Ͱ͓ؾʹೖΓʹ௥Ճͨ͠Βɺ͓ؾʹೖΓҰཡը໘ʹ ΋߲໨Λ௥Ճ͢ΔActionΛσΟεύον͢Ε͹ྑ͍ • Ұཡը໘͔Βݕࡧ৚݅બ୒ը໘ʹભҠ͠ɺݕࡧ৚݅Λબ୒ͨ͠ ΒҰཡը໘ʹ໭ͬͯݕࡧΛ࣮ߦ •

    ݕࡧ৚݅બ୒ը໘Ͱݕࡧ৚݅Λબ୒ͨ͠ΒɺҰཡը໘ͷݕࡧ ৚݅Λมߋ͢ΔActionΛσΟεύον͢Ε͹ྑ͍
  39. ࠓͷͱ͜ΖReduxʢReSwiftʣྑͦ͞͏

  40. ·ͱΊ • ReduxύλʔϯΛಋೖ͢Δ͜ͱͰɺෳ਺ը໘ؒͰͷঢ়ଶͷڞ༗ ํ๏ɺঢ়ଶมԽͷ௨஌ํ๏ʹ͍ͭͯͷ౷Ұͨ͠ϧʔϧ͕ੜ·Ε Δ • MVVM+ReduxύλʔϯʹΑͬͯΞϓϦશମͰίʔυͷએݴత ͳهड़͕ՄೳʹͳΓɺಡΈ΍͘͢อक͠΍͍͢ίʔυʹͳΔ

  41. 5IBOLZPV