Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
健康的なMVVM 書いてますか? ~MVVMアンチパターン集~
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
takasek
April 27, 2016
Programming
26
21k
健康的なMVVM 書いてますか? ~MVVMアンチパターン集~
Health Swift Meetup (
http://finc-swift.connpass.com/event/29901/
) の発表資料です。
takasek
April 27, 2016
Tweet
Share
More Decks by takasek
See All by takasek
影響スケッチでFatViewControllerを可視化してみよう' / 20200530 effect sketch #swiftzoomin
takasek
1
1.7k
Clean Architecture: クライアントアプリの「中心」とは何か / 20200121 the center of the client #ios_ca
takasek
10
3.8k
SOLID原則を生活に適用する / 20190906 iOSDC
takasek
4
13k
APIレスポンスにおける直和型の表現を考える / 20190730 sekkeikaigi
takasek
4
1.6k
Dark Mode / 20190617 #wwdc_rusuban
takasek
11
1.5k
継続渡しと契約による設計 / 20190319 #tryswift_pre
takasek
8
2.2k
Continuation-Passing Style and Design By Contract(English ver.) / 20190319(E) #tryswift_pre
takasek
1
340
FiNCのクライアントアーキテクチャを揃える試み / 20190110 #app_mp
takasek
1
7.2k
SOLID原則のSとDとテストの話 - 「Swiftらしく設計する」Another / 20181221 #roppongiswift
takasek
1
1.4k
Other Decks in Programming
See All in Programming
20260228_JAWS_Beginner_Kansai
takuyay0ne
5
470
2026/02/04 AIキャラクター人格の実装論 口 調の模倣から、コンテキスト制御による 『思想』と『行動』の創発へ
sr2mg4
0
730
The Past, Present, and Future of Enterprise Java
ivargrimstad
0
340
エラーログのマスキングの仕組みづくりに役立ったASTの話
kumoichi
0
130
AI Assistants for Your Angular Solutions
manfredsteyer
PRO
0
110
株式会社 Sun terras カンパニーデック
sunterras
0
2k
Codexに役割を持たせる 他のAIエージェントと組み合わせる実務Tips
o8n
3
1.2k
CopilotKit + AG-UIを学ぶ
nearme_tech
PRO
2
150
Claude Code、ちょっとした工夫で開発体験が変わる
tigertora7571
0
200
今、アーキテクトとして 品質保証にどう関わるか
nealle
0
210
TipKitTips
ktcryomm
0
160
ご飯食べながらエージェントが開発できる。そう、Agentic Engineeringならね。
yokomachi
1
290
Featured
See All Featured
Public Speaking Without Barfing On Your Shoes - THAT 2023
reverentgeek
1
330
Speed Design
sergeychernyshev
33
1.6k
Art, The Web, and Tiny UX
lynnandtonic
304
21k
Have SEOs Ruined the Internet? - User Awareness of SEO in 2025
akashhashmi
0
280
Collaborative Software Design: How to facilitate domain modelling decisions
baasie
0
150
AI Search: Where Are We & What Can We Do About It?
aleyda
0
7.1k
Agile Actions for Facilitating Distributed Teams - ADO2019
mkilby
0
140
Sam Torres - BigQuery for SEOs
techseoconnect
PRO
0
210
Documentation Writing (for coders)
carmenintech
77
5.3k
How STYLIGHT went responsive
nonsquared
100
6k
ラッコキーワード サービス紹介資料
rakko
1
2.6M
コードの90%をAIが書く世界で何が待っているのか / What awaits us in a world where 90% of the code is written by AI
rkaga
60
42k
Transcript
݈߁తͳ.77. ॻ͍ͯ·͔͢ʁ d.77.Ξϯνύλʔϯूd )FBMUI4XJGU.FFUVQ CZ XJUI'J/$
!UBLBTFL w GSFFMBODFJ04&OHJOFFS w 'J/$͞ΜͰ͓ࣄ͍͍ͤͯͨͩͯ͞·͢ w ɹ!UBLBTFL w ɹUBLBTFL w
ɹUBLBTFL
!UBLBTFL w /PUJGXJGU IUUQTHJUIVCDPNUBLBTFL/PUJGXJGU /4/PUJpDBUJPOͷVTFS*OGPΛ 4XJGUZʹѻ͏ϚΠΫϩϥΠϒϥϦ w "DUJPO$MPTVSBCMF IUUQTHJUIVCDPNUBLBTFL"DUJPO$MPTVSBCMF UBSHFUBDUJPOΛ͢ॲཧΛ
4XJGUZͳΫϩʔδϟͰॻ͚Δ ϚΠΫϩϥΠϒϥϦ (JU)VCͰ 044ϥΠϒϥϦ࡞ͬͯ·͢
͓͞Β͍ .77.ͱ
.PEFM 7JFX.PEFM 7JFX %BUB #JOEJOH $PNNBOET ɹ%BUB#JOEJOHͷ࣮ݱͷͨΊʹɺJ04ͷ߹ ɹɹ'31ϥΠϒϥϦ 3Y4XJGU 3FBDUJWF$PDPB
ɹɹσʔλόΠϯσΟϯάϥΠϒϥϦ 4XJGU#POE ͷ ɹɹαϙʔτ͕ඞཁ ɹ ˞'J/$Ͱݱࡏ4XJGU#POEΛར༻
ຊ
͜Ε͔Β ෆ݈߁ͳ7JFX.PEFMΛ ͠·͢
Ͱ͠ΌΓ7JFX.PEFM Χϧςᶃ
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ͷࢀরΛ࣋ͬͯɺૢ࡞͢Δ
Ͱ͠ΌΓ7JFX.PEFM පࠜ ࠜຊతʹઃܭ͕͓͔͍͠ w 7JFXʹमਖ਼͕ೖͬͨΒ7JFX.PEFMमਖ਼͠ͳ͖Όʜ w ͔ͤͬ͘ͷ.77.ύλʔϯ͕ແͩ͠ʂ w ʜͱ͍͏͔ɺ͜Ε.77.ʹͳͬͯͳ͍
ґଘͷํ 7JFXɹɹ7JFX.PEFM 7JFX.PEFMɹɹ.PEFM Ҿ༻"SDIJUFDUJOH"OESPJEʜ5IFDMFBOXBZ IUUQGFSOBOEPDFKBTDPNBSDIJUFDUJOHBOESPJEUIFDMFBOXBZ 7JFX.PEFM 7JFX σʔλόΠϯσΟϯά
Ͱ͠ΌΓ7JFX.PEFM ॲํᝦ w 7JFX.PEFMࣗࣗͷঢ়ଶΛมߋ͢Δ͚ͩ w Ͳ͏ΘΕΔ͔Ұؔ͠ͳ͍͠ɺ ୭ 7JFX ʹόΠϯυ͞Ε͍ͯΔ͔Βͳ͍ ʹ7JFXʹґଘ͠ͳ͍
ʹมߋʹڧ͍ 7JFX.PEFMˠ7JFX ඞͣόΠϯσΟϯάͰܨ͙
class MyViewModel { let fuga = Observable<Fuga?>(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 վળྫ
ࠞઢ͍ͯ͠Δ 7JFX.PEFM Χϧςᶄ
ࠞઢ͍ͯ͠Δ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Λ ड͚औΔ
ࠞઢ͍ͯ͠Δ7JFX.PEFM පࠜ ॲཧͷྲྀΕ͕ ཧͰ͖͍ͯͳ͍
ࠞઢ͍ͯ͠Δ7JFX.PEFM ॲํᝦ جຊɺ 7JFXˠ7JFX.PEFMίϚϯυΛୟ͚ͩ͘ 7JFX.PEFMˠ7JFXόΠϯυ͢Δ͚ͩ 7JFX.PEFM 7JFX %BUB #JOEJOH $PNNBOET
ᶃίϚϯυ ᶄঢ়ଶมߋ ᶅঢ়ଶө †એݴతʹهड़
Ψϥεͷ7JFX.PEFM Χϧςᶅ
class MyViewModel { let text = Observable<String>("") let textLength =
Observable<Int>(0) } vm.text.value = "ͳΜ͔͍ςΩετ" vm.text // "ͳΜ͔͍ςΩετ" vm.textLength // 0 ←!!?!!? Ψϥεͷ7JFX.PEFM ঢ় յΕͦ͏ͳ0CTFSWBCMF͔ΓूΊͯ͠·͏
Ψϥεͷ7JFX.PEFM පࠜ ঢ়ଶͷओैؔΛ એݴతʹදݱͰ͖͍ͯͳ͍ w UFYUΛมߋͨ͠Βɺ ͦͷϝιουͰUFYU-FOHUI มߋ͢ΔΑ͏ʹؾΛ͚ͭΔʁ ͍͍ɺͦΜͳͷਓ͕ؒ έΞ͖͢͜ͱ͡Όͳ͍͔Βʂ
ຖճҪށ͔Β ਫΛ·͞ΕͯΔΑ͏ͳ ॏ࿑ಇײ
Ψϥεͷ7JFX.PEFM ॲํᝦ $PMEͳ0CTFSWBCMFΛ͏ )PU 0CTFSWBCMF $PME &WFOU1SPEVDFS
class MyViewModel { let text = Observable<String>("") let textLength: EventProducer<Int>
init() { textLength = text.map { $0.characters.count } } } vm.text // "ͳΜ͔͍ςΩετ" vm.textLength // 9 Ψϥεͷ7JFX.PEFM վળྫ ɹUFYU-FOHUINBQ USBOTGPSNJOHPQFSBUPS Λ௨͚ͩ͢ͷ ɹ$PMEͳଘࡏʹ͠ɺ߹ੑΛอূ એݴతʹهड़Ͱ͖ͨʂ
ઉΒͣͳ 7JFX.PEFM Χϧςᶆ
class MyViewModel { let name = Observable<String>("") let address =
Observable<String>("") } 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 ͲͪΒ͕มߋ͞Εͯ ྆ํΛͬͯߋ৽ॲཧΛ ߦ͍͍ͨʂ
ઉΒͣͳ7JFX.PEFM පࠜ σʔλΛదͳཻɾͰ ͍ͯ͠ͳ͍
ઉΒͣͳ7JFX.PEFM ॲํᝦ ֤छΦϖϨʔλΛ͍͜ͳ͢ class MyViewModel { let name = Observable<String>("")
let address = Observable<String>("") 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͕ λϓϧͰͬͯ͘Δ
ಜෆߦಧͳ 7JFX.PEFM Χϧςᶇ
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ଆʹঢ়ଶ݅ذ͕͋Δ
ಜෆߦಧͳ7JFX.PEFM පࠜ ঢ়ଶ݅அͷίʔυΛ 7JFX.PEFMʹҠ͍ͯ͠ͳ͍
ͦͦ7JFX.PEFM.PEFMͷӨͳͷͰ͢ɻ ͦͯ͠·ͨ7JFX7JFX.PEFMͷӨͰ͋Γ·͢ɻ Ҿ༻.77.ͷ.PEFMʹ·ͭΘΔޡղUIFTFBPGGFSUJMJUZ IUUQVHBZBIBUFCMPKQFOUSZNPEFMNJTUBLF
ಜෆߦಧͳ7JFX.PEFM ॲํᝦ ʮ7JFX7JFX.PEFMͷӨʯΛ పఈͤ͞Δ class MyView { func awakeFromNib() {
viewModel.title.observe { ... } viewModel.image.observe { ... } viewModel.detail.observe { ... } } } 7JFXԿஅΛߦΘͣɺ 7JFX.PEFMͷঢ়ଶΛ ʑͱ6*ʹөͤ͞Δ 7JFX.PEFMɺ 7JFXΛ࠶ߏͰ͖Δ͚ͩͷ ঢ়ଶใΛอ࣋͢Δ
ਆܦ࣭ͳ 7JFX.PEFM Χϧςᶈ
ਆܦ࣭ͳ7JFX.PEFM ঢ় 7JFX.PEFM͕ঢ়ଶΛࡉ͔࣋ͪ͗ͯ͘͢ΧΦε ɾμΠΞϩάͷදࣔඇදࣔঢ়ଶͱ͔ ɾը໘ભҠͷঢ়ଶͱ͔
ਆܦ࣭ͳ7JFX.PEFM පࠜ ʮঢ়ଶʯͱʮشൃੑͷݱʯΛ ۠ผͰ͖͍ͯͳ͍ ˞شൃੑͷݱඞͣফ͑Ώ͘ͷɻ
ਆܦ࣭ͳ7JFX.PEFM ॲํᝦ 0CTFSWBCMF7PJE class MyViewModel { let isUpdated = Observable<Void>()
private func didSomething() { isUpdated.next() } } 81' 8JOEPXT1SFTFOUBUJPO'PVOEBUJPO ʹ .FTTFOHFSͱ͍͏֓೦͕͋Δ 7JFX.PEFM͕Πϕϯτͷൃߦͱ͍͏ܗͰ7JFXʹ௨͢ΔΈ ղܾ͍ͨ͠ಉ͡ʜ ͩͱࢥ͏Μ͚ͩͲɺ 81'ͷݟ͕ͳ͍ͷͰ ؒҧͬͯͨΒ͢Έ·ͤΜʜ ͱΓ͋͑ͣ 0CTFSWBCMF7PJEศརͰ͢
ϝλϘϦοΫγϯυϩʔϜ 7JFX.PEFM Χϧςᶉ
ϝλϘͳ7JFX.PEFM ঢ় 7JFX.PEFM͕ ɹ ɹ௨৴ॲཧ ɹσʔλΩϟογϡ ɹ͞·͟·ͳۀͷϋϯυϦϯά ɹʜΛߦ͍ͬͯΔ
ϝλϘͳ7JFX.PEFM පࠜ .PEFMʹ͍ͭͯ ޡղͯ͠·ͤΜ͔
ϝλϘͳ7JFX.PEFM ॲํᝦ .PEFMΛ͔ͬ͠Γ࡞Ζ͏ .PEFM%"0 %BUB"DDFTT0CKFDU Ͱͳ͍ .PEFMۀϩδοΫ ඇ6*,JUͷผϓϥοτϑΥʔϜʹҠ২ͯ͠มΘΒͳ͍෦ Α͘7JFX$POUSPMMFSʹߦ͘Β͍ॻ͔ΕͯΔͭ ͋Ε΄ͱΜͲ.PEFMʹॻ͖͘ͷ
;ͨͨͼҾ༻"SDIJUFDUJOH"OESPJEʜ5IFDMFBOXBZ IUUQGFSOBOEPDFKBTDPNBSDIJUFDUJOHBOESPJEUIFDMFBOXBZ .77. ΞʔΩςΫνϟશମΛΧόʔͰ͖Δ֓೦Ͱͳ͘ ද෦ͷ࣮ʹա͗ͳ͍
.PEFM 7JFX.PEFM 7JFX %BUB #JOEJOH $PNNBOET ͜ͷ࿈ܞʹ͍ͭͯ.77.Կنఆͯ͠ͳ͍ ʢ.7˓શ෦ͦ͏͚ͩͲʣ
ͦ͏͍͏ͱ͜Λҙࣝͭͭ͠ɺ ΞϓϦશମʹΛͬͯ ݈߁తͳઃܭΛ͠ɺ ݈߁తͳίʔυΛॻ͖ɺ ݈߁తͳΤϯδχΞϥΠϑΛૹΓ·͠ΐ͏ʂ
'J/$Ͱ ݈߁తͳΤϯδχΞؒΛืू͍ͯ͠·͢