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. iOSΞϓϦ։ൃʹ
    MVVM+Reduxύλʔϯ
    Λ࢖͍࢝Ίͨ࿩
    SwiftѪ޷ձ vol36
    2018/11/09
    @takehilo_kaneko

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  8. MVVMͷ՝୊
    • ҰཡλϒͰ͓ؾʹೖΓ௥Ճˠ͓ؾʹೖΓҰཡλϒʹଈ൓ө
    • ֤ViewController͸ผʑͷViewModelʹґଘ͍ͯ͠Δ
    • ViewModelؒͰঢ়ଶมԽΛͲ͏௨஌͢Δʁ
    • Ұཡը໘͔Βৄࡉը໘ʹભҠ࣌ʹϞσϧ΍ঢ়ଶΛ౉͢
    • Ϟσϧ΍ঢ়ଶΛViewControllerʹ౉͢ʁViewModelʹ౉͢ʁ
    • ౉͢ύϥϝʔλ͕ଟ͍ͱɺίʔυมߋ࣌ʹ౉͠๨Ε͕ग़͖ͯ
    ͦ͏

    View full-size slide

  9. MVVMͷ՝୊
    • Ұཡը໘͔Βݕࡧ৚݅બ୒ը໘ʹભҠ͠ɺݕࡧ৚݅Λબ୒ͨ͠
    ΒҰཡը໘ʹ໭ͬͯݕࡧΛ࣮ߦ
    • Ͳ͏΍ͬͯલͷը໘ʹঢ়ଶΛ౉͢ʁ
    • DelegateύλʔϯΛ࢖͏ɺҰཡը໘༻ViewModelΛݕࡧ৚݅
    બ୒ը໘Ͱ΋ڞ༗͢ΔɺͳͲͷ΍Γํ͕ߟ͑ΒΕΔ

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  18. ReSwift
    ެࣜReferenceΑΓ
    • ReduxϥΠΫͳ୯ํ޲σʔλϑϩʔΞʔΩςΫνϟͷ࣮૷
    • ΞϓϦΛ3ͭͷؔ৺ࣄʹ෼཭͢Δ
    • State: ΞϓϦશମͷঢ়ଶΛҰͭͷσʔλߏ଄Ͱදݱ
    • Views: ݱࡏͷΞϓϦͷঢ়ଶΛࢹ֮Խ͢Δ
    • State Changes: ঢ়ଶมߋͷํ๏Λݫ੍͘͠ݶ
    http://reswift.github.io/ReSwift/master/index.html

    View full-size slide

  19. ಡॻϝϞҰཡදࣔը໘Λྫʹ࣮૷Λݟ͍ͯ͘

    View full-size slide

  20. Stateͷ࣮૷

    View full-size slide

  21. struct AppState: StateType {
    var postListState = PostListState()
    }
    • ΞϓϦͷঢ়ଶΛstructͰදݱ͢Δ
    • ը໘୯ҐͳͲͰঢ়ଶΛ֊૚Խ͍ͯ͘͠

    View full-size slide

  22. let appStore = Store(
    reducer: appReducer,
    state: AppState(),
    middleware: [])
    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
    • ReSwiftͷ࢓૊ΈΛಈ͔ͨ͢Ίɺ࠷ॳʹStoreΛηοτΞοϓ͢Δ
    • ৔ॴ͸Ͳ͜Ͱ΋ྑ͍͕ɺੜ੒ͨ͠StoreΠϯελϯεΛอ࣋ͯ͠
    ͓͘

    View full-size slide

  23. View + ViewModelͷ࣮૷

    View full-size slide

  24. class PostListViewModel: StoreSubscriber {
    init(store: Store) {
    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ʹ௨஌

    View full-size slide

  25. 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ػೳʹప͢Δ
    • ঢ়ଶʹґଘͨ͠ϩδοΫ͸ۃྗ࣋ͨͤͳ͍

    View full-size slide

  26. Action + Reducerͷ࣮૷

    View full-size slide

  27. class PostListViewModel: StoreSubscriber {
    init(store: Store) {
    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͕σΟεύον
    ͞ΕΔ

    View full-size slide

  28. extension PostListState {
    static func listenPostsActionCreator(state: AppState, store: Store) ->
    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
    Λੜ੒ͯ͠σΟεύον͢Δ

    View full-size slide

  29. 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ʹ௨஌͞Εɺը໘ʹσʔ
    λ͕දࣔ͞ΕΔ

    View full-size slide

  30. ͜ͷ࢓૊ΈͰMVVMͷ՝୊͕ղܾͰ͖Δ͔

    View full-size slide

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

    View full-size slide

  32. 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Λੜ੒
    ͠ɺબ୒͞Εͨߦͷσʔλ΍ԿΒ͔ͷঢ়ଶσʔλΛ౉ͯ͠ॳظ
    Խ͢Δඞཁ͕͋Δ

    View full-size slide

  33. 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ʹ௨஌͢Δ͜ͱͰɺৄࡉը໘
    ༻ͷঢ়ଶ͕มߋ͞ΕΔ
    • ͋ͱ͸Կ΋ߟ͑ͣৄࡉը໘Λදࣔͯ͋͛͠Ε͹ɺৄࡉը໘ଆ͸
    উखʹ࠷৽ͷঢ়ଶΛ࢖ͬͯදࣔΛͯ͘͠ΕΔ

    View full-size slide

  34. ᶃ selectPostΞΫγϣϯ
    ͕σΟεύον͞ΕΔ
    ᶄ ৄࡉը໘༻ͷঢ়ଶͰ͋Δ
    PostState͕มߋ͞ΕΔ
    ᶅ ৄࡉը໘͕࠷৽ͷ
    PostStateͰදࣔ͞ΕΔ

    View full-size slide

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

    View full-size slide

  36. ࠓͷͱ͜ΖReduxʢReSwiftʣྑͦ͞͏

    View full-size slide

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

    View full-size slide