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

健康的なMVVM 書いてますか? ~MVVMアンチパターン集~

健康的なMVVM 書いてますか? ~MVVMアンチパターン集~

Health Swift Meetup (http://finc-swift.connpass.com/event/29901/) の発表資料です。

takasek

April 27, 2016
Tweet

More Decks by takasek

Other Decks in Programming

Transcript

  1. ݈߁తͳ.77.
    ॻ͍ͯ·͔͢ʁ
    d.77.Ξϯνύλʔϯूd
    )FBMUI4XJGU.FFUVQ
    CZ
    XJUI'J/$

    View Slide

  2. !UBLBTFL
    w GSFFMBODFJ04&OHJOFFS
    w 'J/$͞ΜͰ͓࢓ࣄ͍͍ͤͯͨͩͯ͞·͢
    w ɹ!UBLBTFL
    w ɹUBLBTFL
    w ɹUBLBTFL

    View Slide

  3. !UBLBTFL
    w /PUJGXJGU

    IUUQTHJUIVCDPNUBLBTFL/PUJGXJGU

    /4/PUJpDBUJPOͷVTFS*OGPΛ

    4XJGUZʹѻ͏ϚΠΫϩϥΠϒϥϦ
    w "DUJPO$MPTVSBCMF

    IUUQTHJUIVCDPNUBLBTFL"DUJPO$MPTVSBCMF

    UBSHFUBDUJPOΛ౉͢ॲཧΛ

    4XJGUZͳΫϩʔδϟͰॻ͚Δ

    ϚΠΫϩϥΠϒϥϦ
    (JU)VCͰ

    044ϥΠϒϥϦ΋࡞ͬͯ·͢

    View Slide

  4. ͓͞Β͍
    .77.ͱ͸

    View Slide

  5. .PEFM
    7JFX.PEFM
    7JFX
    %BUB
    #JOEJOH
    $PNNBOET
    ɹ%BUB#JOEJOHͷ࣮ݱͷͨΊʹ͸ɺJ04ͷ৔߹
    ɹɹ'31ϥΠϒϥϦ 3Y4XJGU 3FBDUJWF$PDPB
    ΍
    ɹɹσʔλόΠϯσΟϯάϥΠϒϥϦ 4XJGU#POE
    ͷ
    ɹɹαϙʔτ͕ඞཁ
    ɹ ˞'J/$Ͱ͸ݱࡏ4XJGU#POEΛར༻

    View Slide

  6. ຊ୊

    View Slide

  7. ͜Ε͔Β

    ෆ݈߁ͳ7JFX.PEFMΛ

    ਍࡯͠·͢

    View Slide

  8. Ͱ͠Ό͹Γ7JFX.PEFM
    Χϧςᶃ


    View Slide

  9. class MyViewModel {
    weak var view: MyView?
    func doSomething(fuga: Fuga) {
    guard let view = view else { return }
    if view.isHoge {
    view.doSomething(fuga)
    }
    }
    }
    class MyView {
    func awakeFromNib() {
    viewModel.view = self
    }
    }
    Ͱ͠Ό͹Γ7JFX.PEFM
    ঱ঢ়
    7JFX.PEFM͕7JFX΁ͷࢀরΛ࣋ͬͯɺ௚઀ૢ࡞͢Δ

    View Slide

  10. Ͱ͠Ό͹Γ7JFX.PEFM
    පࠜ

    ࠜຊతʹઃܭ͕͓͔͍͠
    w 7JFXʹमਖ਼͕ೖͬͨΒ7JFX.PEFM΋मਖ਼͠ͳ͖Όʜ
    w ͔ͤͬ͘ͷ.77.ύλʔϯ͕୆ແͩ͠ʂ
    w ʜͱ͍͏͔ɺ͜Ε.77.ʹͳͬͯͳ͍

    View Slide

  11. ґଘͷํ޲͸
    7JFXɹɹ7JFX.PEFM
    7JFX.PEFMɹɹ.PEFM
    Ҿ༻"SDIJUFDUJOH"OESPJEʜ5IFDMFBOXBZ 

    IUUQGFSOBOEPDFKBTDPNBSDIJUFDUJOHBOESPJEUIFDMFBOXBZ
    7JFX.PEFM 7JFX
    σʔλόΠϯσΟϯά

    View Slide

  12. Ͱ͠Ό͹Γ7JFX.PEFM
    ॲํᝦ
    w 7JFX.PEFM͸ࣗ෼ࣗ਎ͷঢ়ଶΛมߋ͢Δ͚ͩ
    w Ͳ͏࢖ΘΕΔ͔͸Ұ੾ؔ஌͠ͳ͍͠ɺ

    ୭ 7JFX
    ʹόΠϯυ͞Ε͍ͯΔ͔΋஌Βͳ͍

    ʹ7JFXʹґଘ͠ͳ͍

    ʹมߋʹڧ͍
    7JFX.PEFMˠ7JFX͸
    ඞͣόΠϯσΟϯάͰܨ͙

    View Slide

  13. class MyViewModel {
    let fuga = Observable(nil)
    func didReceiveFuga(fuga: Fuga) {
    self.fuga.value = fuga
    }
    }
    class MyView {
    func awakeFromNib() {
    viewModel.fuga.ignoreNil().observe { [weak self] in
    self?.doSomething($0)
    }
    }
    }
    Ͱ͠Ό͹Γ7JFX.PEFM
    վળྫ

    View Slide

  14. ࠞઢ͍ͯ͠Δ
    7JFX.PEFM
    Χϧςᶄ


    View Slide

  15. ࠞઢ͍ͯ͠Δ7JFX.PEFM
    ঱ঢ়
    7JFX.PEFM΁ͷίϚϯυ͕

    ɹॲཧͷ׬ྃ࣌ʹ࣮ߦ͢ΔΫϩʔδϟΛड͚ͨΓ
    class MyView {
    func awakeFromNib() {
    viewModel.alertMessage.observe { [weak self] in
    self?.showAlert($0) // ↓Ͳ͕ͬͪຊے!?
    }
    }
    func didTapButton() {
    viewModel.doSomething(completion: { [weak self] alertMessage in
    self?.showAlert(alertMessage) // ↑Ͳ͕ͬͪຊے!?
    })
    }
    }
    ˠॲཧͷྲྀΕ͕ΧΦεʹʂ
    BMFSU.FTTBHF
    0CTFSWBCMF4USJOH͕
    มߋ͞ΕͨΒൃಈ
    ͳΜ͔΍ͬͨ݁Ռ
    BMFSU.FTTBHFΛ
    ड͚औΔ

    View Slide

  16. ࠞઢ͍ͯ͠Δ7JFX.PEFM
    පࠜ

    ॲཧͷྲྀΕ͕
    ੔ཧͰ͖͍ͯͳ͍

    View Slide

  17. ࠞઢ͍ͯ͠Δ7JFX.PEFM
    ॲํᝦ
    جຊɺ

    7JFXˠ7JFX.PEFM͸ίϚϯυΛୟ͚ͩ͘
    7JFX.PEFMˠ7JFX͸όΠϯυ͢Δ͚ͩ
    7JFX.PEFM
    7JFX
    %BUB
    #JOEJOH
    $PNNBOET
    ᶃίϚϯυ
    ᶄঢ়ଶมߋ
    ᶅঢ়ଶ൓ө
    †એݴతʹهड़

    View Slide

  18. Ψϥεͷ7JFX.PEFM
    Χϧςᶅ


    View Slide

  19. class MyViewModel {
    let text = Observable("")
    let textLength = Observable(0)
    }
    vm.text.value = "ͳΜ͔௕͍ςΩετ"
    vm.text // "ͳΜ͔௕͍ςΩετ"
    vm.textLength // 0 ←!!?!!?
    Ψϥεͷ7JFX.PEFM
    ঱ঢ়
    յΕͦ͏ͳ0CTFSWBCMF͹͔ΓूΊͯ͠·͏

    View Slide

  20. Ψϥεͷ7JFX.PEFM
    පࠜ

    ঢ়ଶͷओैؔ܎Λ

    એݴతʹදݱͰ͖͍ͯͳ͍
    w UFYUΛมߋͨ͠Βɺ

    ͦͷϝιουͰUFYU-FOHUI΋

    มߋ͢ΔΑ͏ʹؾΛ͚ͭΔʁ

    ͍΍͍΍ɺͦΜͳͷਓ͕ؒ

    έΞ͢΂͖͜ͱ͡Όͳ͍͔Βʂ
    ຖճҪށ͔Β
    ਫΛ἞·͞ΕͯΔΑ͏ͳ
    ॏ࿑ಇײ

    View Slide

  21. Ψϥεͷ7JFX.PEFM
    ॲํᝦ
    $PMEͳ0CTFSWBCMFΛ࢖͏
    )PU

    0CTFSWBCMF

    $PME

    &WFOU1SPEVDFS

    View Slide

  22. class MyViewModel {
    let text = Observable("")
    let textLength: EventProducer
    init() {
    textLength = text.map { $0.characters.count }
    }
    }
    vm.text // "ͳΜ͔௕͍ςΩετ"
    vm.textLength // 9
    Ψϥεͷ7JFX.PEFM
    վળྫ
    ɹUFYU-FOHUI͸NBQ USBOTGPSNJOHPQFSBUPS
    Λ௨͚ͩ͢ͷ
    ɹ$PMEͳଘࡏʹ͠ɺ੔߹ੑΛอূ
    એݴతʹهड़Ͱ͖ͨʂ

    View Slide

  23. ઉ଍Βͣͳ

    7JFX.PEFM
    Χϧςᶆ


    View Slide

  24. class MyViewModel {
    let name = Observable("")
    let address = Observable("")
    }
    let vm = MyViewModel()
    vm.name.observe {
    func setUserData()
    }
    vm.address.observe {
    func setUserData()
    }
    func setUserData(userId: Int) {
    userNameLabel.text = vm.name
    userAddressLabel.text = vm.address
    }
    ઉ଍Βͣͳ7JFX.PEFM
    ঱ঢ়
    ɹ7JFX.PEFMͷมԽΛड͚औͬͨ7JFX͕ɺ

    ɹվΊͯ7JFX.PEFMͷϓϩύςΟΛࢀর͍ͯ͠Δ
    OBNFͱBEESFTT
    ͲͪΒ͕มߋ͞Εͯ΋
    ྆ํΛ࢖ͬͯߋ৽ॲཧΛ
    ߦ͍͍ͨʂ

    View Slide

  25. ઉ଍Βͣͳ7JFX.PEFM
    පࠜ
    σʔλΛద੾ͳཻ౓ɾ૊Ͱ
    ౉͍ͯ͠ͳ͍

    View Slide

  26. ઉ଍Βͣͳ7JFX.PEFM
    ॲํᝦ
    ֤छΦϖϨʔλΛ࢖͍͜ͳ͢
    class MyViewModel {
    let name = Observable("")
    let address = Observable("")
    lazy var nameAndAddress: EventProducer<(String, String)> = {
    return combineLatest(self.name, self.address)
    }()
    }
    let vm = MyViewModel()
    vm.nameAndAddress.observe { name, address in
    userNameLabel.text = name
    userAddressLabel.text = address
    }
    IUUQSYNBSCMFTDPNͱ͔ࢀߟʹͳΔΑ
    OBNFͱBEESFTT͕
    λϓϧͰ౉ͬͯ͘Δ

    View Slide

  27. ؂ಜෆߦಧͳ

    7JFX.PEFM
    Χϧςᶇ


    View Slide

  28. class MyView {
    var viewType: ViewType
    func setup(viewType: ViewType) {
    self.viewType = viewType
    switch viewType {
    case .Title:
    viewModel.title.observe {
    ...
    }
    case .Image:
    viewModel.image.observe {
    ...
    }
    case .Detail:
    viewModel.detail.observe {
    ...
    }
    }
    }
    }
    ؂ಜෆߦಧͳ7JFX.PEFM
    ঱ঢ়
    7JFXଆʹঢ়ଶ΍৚݅෼ذ͕͋Δ

    View Slide

  29. ؂ಜෆߦಧͳ7JFX.PEFM
    පࠜ

    ঢ়ଶ΍৚݅൑அͷίʔυΛ

    7JFX.PEFMʹҠ͍ͯ͠ͳ͍

    View Slide

  30. ͦ΋ͦ΋7JFX.PEFM͸.PEFMͷӨͳͷͰ͢ɻ

    ͦͯ͠·ͨ7JFX͸7JFX.PEFMͷӨͰ΋͋Γ·͢ɻ
    Ҿ༻.77.ͷ.PEFMʹ·ͭΘΔޡղUIFTFBPGGFSUJMJUZ
    IUUQVHBZBIBUFCMPKQFOUSZNPEFMNJTUBLF

    View Slide

  31. ؂ಜෆߦಧͳ7JFX.PEFM
    ॲํᝦ
    ʮ7JFX͸7JFX.PEFMͷӨʯΛ
    పఈͤ͞Δ
    class MyView {
    func awakeFromNib() {
    viewModel.title.observe {
    ...
    }
    viewModel.image.observe {
    ...
    }
    viewModel.detail.observe {
    ...
    }
    }
    }
    7JFX͸Կ΋൑அΛߦΘͣɺ
    7JFX.PEFMͷঢ়ଶΛ
    ໧ʑͱ6*ʹ൓өͤ͞Δ
    7JFX.PEFM͸ɺ
    7JFXΛ࠶ߏ੒Ͱ͖Δ͚ͩͷ
    ঢ়ଶ৘ใΛอ࣋͢Δ

    View Slide

  32. ਆܦ࣭ͳ

    7JFX.PEFM
    Χϧςᶈ


    View Slide

  33. ਆܦ࣭ͳ7JFX.PEFM
    ঱ঢ়
    7JFX.PEFM͕ঢ়ଶΛࡉ͔࣋ͪ͗ͯ͘͢ΧΦε
    ɾμΠΞϩάͷදࣔඇදࣔঢ়ଶͱ͔
    ɾը໘ભҠͷঢ়ଶͱ͔

    View Slide

  34. ਆܦ࣭ͳ7JFX.PEFM
    පࠜ
    ʮঢ়ଶʯͱʮشൃੑͷݱ৅ʯΛ
    ۠ผͰ͖͍ͯͳ͍
    ˞شൃੑͷݱ৅ඞͣফ͑Ώ͘΋ͷɻ

    View Slide

  35. ਆܦ࣭ͳ7JFX.PEFM
    ॲํᝦ
    0CTFSWBCMF7PJE
    class MyViewModel {
    let isUpdated = Observable()
    private func didSomething() {
    isUpdated.next()
    }
    }
    81' 8JOEPXT1SFTFOUBUJPO'PVOEBUJPO
    ʹ͸
    .FTTFOHFSͱ͍͏֓೦͕͋Δ

    7JFX.PEFM͕Πϕϯτͷൃߦͱ͍͏ܗͰ7JFXʹ௨஌͢Δ࢓૊Έ
    ղܾ͍ͨ͠໰୊͸ಉ͡ʜ
    ͩͱࢥ͏Μ͚ͩͲɺ
    81'ͷ஌ݟ͕ͳ͍ͷͰ
    ؒҧͬͯͨΒ͢Έ·ͤΜʜ
    ͱΓ͋͑ͣ
    0CTFSWBCMF7PJE͸ศརͰ͢

    View Slide

  36. ϝλϘϦοΫγϯυϩʔϜ

    7JFX.PEFM
    Χϧςᶉ


    View Slide

  37. ϝλϘͳ7JFX.PEFM
    ঱ঢ়
    7JFX.PEFM͕

    ɹ
    ɹ௨৴ॲཧ

    ɹσʔλΩϟογϡ

    ɹ͞·͟·ͳۀ຿ͷϋϯυϦϯά

    ɹʜΛߦ͍ͬͯΔ

    View Slide

  38. ϝλϘͳ7JFX.PEFM
    පࠜ
    .PEFMʹ͍ͭͯ
    ޡղͯ͠·ͤΜ͔

    View Slide

  39. ϝλϘͳ7JFX.PEFM
    ॲํᝦ
    .PEFM૚Λ͔ͬ͠Γ࡞Ζ͏
    .PEFM%"0 %BUB"DDFTT0CKFDU
    Ͱ͸ͳ͍
    .PEFMۀ຿ϩδοΫ
    ඇ6*,JUͷผϓϥοτϑΥʔϜʹҠ২ͯ͠΋มΘΒͳ͍෦෼
    Α͘7JFX$POUSPMMFSʹߦ͘Β͍ॻ͔ΕͯΔ΍ͭ
    ͋Ε͸΄ͱΜͲ.PEFMʹॻ͘΂͖΋ͷ

    View Slide

  40. ;ͨͨͼҾ༻"SDIJUFDUJOH"OESPJEʜ5IFDMFBOXBZ 

    IUUQGFSOBOEPDFKBTDPNBSDIJUFDUJOHBOESPJEUIFDMFBOXBZ
    .77.͸
    ΞʔΩςΫνϟશମΛΧόʔͰ͖Δ֓೦Ͱ͸ͳ͘
    ද૚෦෼ͷ࣮૷ʹա͗ͳ͍

    View Slide

  41. .PEFM
    7JFX.PEFM
    7JFX
    %BUB
    #JOEJOH
    $PNNBOET
    ͜ͷ࿈ܞʹ͍ͭͯ͸.77.͸Կ΋نఆͯ͠ͳ͍
    ʢ.7˓શ෦ͦ͏͚ͩͲʣ

    View Slide

  42. ͦ͏͍͏ͱ͜Λҙࣝͭͭ͠ɺ
    ΞϓϦશମʹ໨Λ഑ͬͯ
    ݈߁తͳઃܭΛ͠ɺ
    ݈߁తͳίʔυΛॻ͖ɺ
    ݈߁తͳΤϯδχΞϥΠϑΛૹΓ·͠ΐ͏ʂ

    View Slide

  43. 'J/$Ͱ͸
    ݈߁తͳΤϯδχΞ஥ؒΛืू͍ͯ͠·͢

    View Slide