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をベースに複雑な振る舞いを しっかり把握できるアプリ開発
Search
yohei sugigami
August 26, 2015
Technology
5.9k
14
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
MVVMをベースに複雑な振る舞いを しっかり把握できるアプリ開発
Realm meetup #6 で発表した
Sync iOS開発の舞台裏についてです
プロジェクトの話や、MVVM、ViewBindingなど多義にわたり解説しています
yohei sugigami
August 26, 2015
More Decks by yohei sugigami
See All by yohei sugigami
Snapshot Testing in iOS
susieyy
6
3.4k
Redux with iOS
susieyy
0
1.4k
Why use Redux in iOS
susieyy
5
2.7k
ReduxRxを活用したアプリアーキテクチャ
susieyy
8
2.4k
Redux+Rxを活用したiOSアプリアーキテクチャ
susieyy
10
2.2k
Swaggerで始めるAPI定義管理とコードジェネレート
susieyy
14
7.8k
開発中のアプリをXcode9 & Swift4に移行しました
susieyy
0
3.8k
Wantedly People ViewModel and Rx
susieyy
7
7.6k
ReduxDevTools' power to the iOS development
susieyy
0
930
Other Decks in Technology
See All in Technology
「AIに依存している」と 「AIを使いこなしている」の違い
k8yasuma
0
120
ヘルスケア領域における AI 活用と その安全性担保のための取り組み (Leveraging AI in Healthcare and Our Efforts to Ensure Its Safety) - Google I/O Extended Tokyo 2026, July 11, 2026
zettaittenani
0
430
AIと共生する開発者プラットフォーム:バクラクのモノレポ×マイクロサービス基盤
sakajunquality
2
4k
Amplify Gen2でbackend.tsにCDKを定義する/しない事によるCDKの挙動の違いとユースケース
smt7174
1
440
穢れた技術選定について
watany
18
5.7k
脱金融のフューチャー・デザイン / Future Design Beyond Finance
ks91
PRO
0
160
人とエージェントが高め合う協業設計
kintotechdev
0
100
生成AI×AWS CDK×AWS FISで"振り返れる"ミニGameDayをつくろう
yoshimi0227
1
440
複数プロダクト組織のAIネイティブ化における戦略 / AICon2026_kude
rakus_dev
0
160
プロダクト開発組織の現在地(Ver.2026/07) / product-organization
kaonavi
0
120
インフラと開発の垣根を超えていき!〜元AWSインフラエンジニアがAWS開発で奮闘している話〜
hatahata021
3
300
Playwright × AI Agent でE2Eテストはどう変わるか AI駆動テストの可能性と実用検証の結果
taiga7543
1
550
Featured
See All Featured
4 Signs Your Business is Dying
shpigford
187
22k
How to Talk to Developers About Accessibility
jct
2
410
Game over? The fight for quality and originality in the time of robots
wayneb77
1
220
Navigating Algorithm Shifts & AI Overviews - #SMXNext
aleyda
1
1.4k
Conquering PDFs: document understanding beyond plain text
inesmontani
PRO
4
2.9k
Leveraging LLMs for student feedback in introductory data science courses - posit::conf(2025)
minecr
1
320
More Than Pixels: Becoming A User Experience Designer
marktimemedia
3
460
VelocityConf: Rendering Performance Case Studies
addyosmani
333
25k
Side Projects
sachag
455
43k
Leo the Paperboy
mayatellez
8
1.9k
Designing Powerful Visuals for Engaging Learning
tmiket
1
450
Done Done
chrislema
186
16k
Transcript
MVVMΛϕʔεʹෳࡶͳৼΔ͍Λ ͔ͬ͠ΓѲͰ͖ΔΞϓϦ։ൃ yohei SUGIGAMI 10/21 shibuya.swift
None
Wantedly
Sync Messenger 8FC %FTLUPQ"QQ "OESPJE BOEJ04 ϏδωείϛϡχέʔγϣϯಛԽϝοηʔδϯάαʔϏε
Sync iOS 4XJGU .77. '31 1SPNJTF Ϟμϯͳཁૉٕज़
Let’s Try TZOD Ұ൪࠷ॳͷݕࡧ݁Ռʹग़Δͣʂ
Projects Summary
Service Architecture $MJFOU$SPTT1MBUGPSN 4FSWFS.JDSPTFSWJDFT
Team "1* 8FC%FTLUPQ J04 "OESPJE GBTUMBOF
iOS Schedule ݄̑ ݄̒ ݄̓ ϓϩτλΠϓ։ൃ 'JSFCBTFબఆ +0*/ ਃ νʔϜ4MBDLஔ͖͑
ࣾ4MBDLஔ͖͑ +0*/ ຊ։ൃ ࣾ֎ϢʔβϑΟʔυόοΫ ϲ݄ɺ̒ਓ݄͙Β͍ +0*/
High Speed Development 13$MPTFE *TTVFT$MPTFE ݄࣌ $PNNJU
Review ϨϏϡʔΞʔΞαΠϯ 5FTU͕͚ͯ͜Δͷ $JSDMF$*ͷௐࢠ͕ѱ͍ɻɻ
Review Ϟνϕʔγϣϯ61Ͱ ੜ࢈ੑ্
Delivery by fastlane 1VMM 3FR .FSHF .BTUFS %FMJWFSZ %FW5FTUFS .BTUFSʹ.FSHF͢ΔʹλΠϜϦʔͳΞϓϦ
͡Ζ͏͘ΜۘͷGBTUpMF
Dog feeding ։ൃ̎िؒͰ4MBDL͔ΒΓ͑Δ ։ൃऀͰ͋Δ͔ࣗͨͪΒ·ͣϑΝϯʹͳΔ ։ൃ̍ϲ݄ͰࣾͰͬͯΒ͍ϑΟʔυόοΫΛΒ͏ ։ൃ̎ϲ݄Ͱۙͳࣾ֎ͷํʑʹͬͯΒ͍͞Βʹ ଟ͘ͷϑΟʔυόοΫΛ͖·ͨ͠
Very Slow Swift Compile ̍ਓ.BD#PPL1SP̎Ͱ։ൃ ίϯύΠϧͪ࣌ؒΛ༗ޮ׆༻ ܕਪͱΫϩδϟʔΩϟϓνϟ͕ͦ͏
Very Slow Swift Compile .BD#PPL1SP JODI -BUF ͰϑϧίϯύΠϧ࣌ؒ ˠɹ̐ඵ ίʔυΛݟͯ͠ίϯύΠϧߴԽ
ˠɹඵʢඵʣ ˠɹࠩίϯύΠϧͷՄೳੑΞοϓ ܕਪ͠ͳ͍ͰܕΛ໌ه͢Δʢ"SSBZ%JDUJPOBSZʣ ෳͷTXJGUϑΝΠϧΛҰͭʹ·ͱΊΔ ܧঝ͠ͳ͍DMBTTpOBMΛ໌ه͢Δ $BSUBHFରԠͷϥΠϒϥϦ1PETͰΠϯετʔϧ͠ͳ͍
App Architecture
MVVM (෩) $POUSPMMFS 7JFX.PEFM .PEFM 3FBDU,JU 4XJGU5BTL 4XJGUZ+40/ 4XJGU#POE 3FBMN
3BX%BUB 1FSTJTUFOU 4UBUF.BOBHFNFOU -PHJD 7JFX%BUB 6*,JU%FMFHBUF #JOEJOH -JCSBSZ 3FTQPOTJCJMJUZ $SFBUF7JFX 4%8FC*NBHF 7JFX7JFX -BZFS
ViewDataBinding 4UBUF 7JFX $POUSPMMFS 7JFX.PEFM %BUB ͱؔ৺ࣄͷ -PHJD 7JFX 7JFX.PEFMͷ-PHJD
4UBUF %BUBͷมߋ͕ 7JFXʹ͍ͭͯؔ͠ͳ͍ PS #JOE 7JFX4UBUF %BUB͕มߋ ͞ΕͨΒಈతʹը໘Λߋ৽ %BUB 4UBUFͷϓϩηεؔ͠ͳ͍ 7JFX #JOEJOH .FUIPE$BMM
Instane Relation 7JFX7JFX $POUSPMMFS 7JFX.PEFM .PEFM ʜ ʜ 3FGFSFODF #VTJOFTT-PHJD
4VC-PHJD 4IBSF-PHJD 4VC-PHJD %BUB %BUB %BUB %BUB %BUB %BUB .BQQJOH #JOEJOH 7JFX #JOEJOH 1SFTFOUBUJPO-PHJD 7JFX.PEFMଞͷϨΠϠʔͷࢀরΛ࣋ͨͳ͍
Request Data Flow Sequence 4XJGUZ+40/ 3FBMN 4XJGU5BTL 4XJGU#POE 1FSTJTUFODF 7JFX#JOEJOH
"1*
Request ViewBinding 4UBUF3FRVFTUJOH 4UBUF&SSPS JUFNT<> 4UBUF/POF *OEJDBUPS7JFX 3FUSZ7JFX /P%BUB7JFX
Request ViewBinding 4UBUF3FRVFTUJOH 4UBUF&SSPS JUFNT<JUFN JUFN JUFN ʜ> 4UBUF/POF *OEJDBUPS7JFX
3FUSZ7JFX /P%BUB7JFX
Request ViewBinding final class RequestListViewModel<T: Identifier> { let items: DynamicArray<T>
= DynamicArray([]) var requestListState = Dynamic<RequestListState>(.None) var noDataFirstViewHidden: Dynamic<Bool> { let a = indicatorViewHidden.map { $0 == false } let b = requestListFirstState.map { $0 == .Error } return reduce(a, b, c) { $0 || $1 == true } } var indicatorViewHidden: Dynamic<Bool> { let a = requestListFirstState.map { $0 != RequestListState.Requesting } let b = items.map { count($0) > 0 } return reduce(a, b) { $0 || $1 == true } } var retryViewHidden: Dynamic<Bool> { let a = requestListFirstState.map { $0 != RequestListState.Error } let b = items.map { count($0) > 0 } return reduce(a, b) { $0 || $1 == true } } 3FRVFTU4UBUFͱ*UFNTͷঢ়ଶͷΑΔڍಈΛએݴ
Request ViewBinding final class ContactsViewController: UIViewController { var tableViewDataSourceBond: UITableViewDataSourceBond<ContactCell>!
let viewModel = ContactsViewModel() let indicatorView = InstantiateFromNib(IndicatorView) let retryView = InstantiateFromNib(RetryView) let noDataView = InstantiateFromNib(NoDataView) override func viewDidLoad() { super.viewDidLoad() viewModel.requestList.indicatorViewHidden ->> indicatorView.dynHidden viewModel.requestList.retryViewHidden ->> retryView.dynHidden viewModel.requestList.noDataFirstViewHidden ->> noDataView.dynHidden } 7JFX$POUSPMMFSͰ7JFX.PEFMͷ4XJGU#POEͱ7JFXΛ#JOEJOH
Request ViewBinding final class RequestListViewModel<T: Identifier> { typealias RequestTask =
Task<Progress, ResponseCollection<T>, NSError> func requestFirst(task: RequestTask) -> RequestTask { self.requestListState.value = .Requesting task.success { [weak self] (collection: ResponseCollection<T>) -> Void in self?.items.setArray(collection.items) self?.requestListState.value = .None }.failure { [weak self] (errorInfo: RequestTask.ErrorInfo) -> Void in self?.requestListState.value = .Error } return task } 3FRVFTUͷ։࢝ɺਖ਼ৗྃɺҎ্ྃͰ4BUFΛߋ৽ ਖ਼ৗྃ࣌ʹJUFNTΛߋ৽
Request ViewBinding final class RequestListViewModel<T: Identifier> { lazy var stateChangedObserver
= Bond<RequestListState> { [weak self] state in switch state { case .Requesting: UIApplication.sharedApplication().networkActivityIndicatorVisible = true default: UIApplication.sharedApplication().networkActivityIndicatorVisible = false } } init() { requestListState ->| stateChangedObserver } OFUXPSL"DUJWJUZ*OEJDBUPS7JTJCMFΛ4UBUFͰ#JOEJOH
Input with Validation ViewBinding &OBCMF4JHO6Q#VUUPO 7BMJEBUJPO&NBJM1BTTXPSE
Validation ViewBinding final class SignUpViewModel { let email = Dynamic<String>("")
let password = Dynamic<String>("") var emailError: Dynamic<NSError?> { return email.map { Validator.Email($0).validate() } } var passwordError: Dynamic<NSError?> { return password.map { Validator.Password($0).validate() } } var isSignInInput: Dynamic<Bool> { let isEmailValid = emailError.map { nil == $0 } let isPasswordValid = passwordError.map { nil == $0 } return reduce(isEmailValid, isPasswordValid) { $0 && $1 == true } } &NBJMͱ1BTTXPSEͷ7BMJEBUJPOʹΑͬͯ 4JHO6Q͕&OBCMFʹͳΔ͔Λએݴ
Validation ViewBinding final class SignUpViewController { override func viewDidLoad() {
super.viewDidLoad() emailTextField ->> viewModel.email passwordTextField ->> viewModel.password viewModel.signUpState ->| signUpStateChangedObserver viewModel.isSignInInput ->> signUpButton.dynEnabled 7JFX$POUSPMMFSͰ7JFX.PEFMͷ4XJGU#POEͱ7JFXΛ#JOEJOH enum Validator { case Email(String?) case Password(String?) func validate() -> NSError? { switch self { case .Email(var value): return NGRValidator.validateValue(value, named: LocalizedString("Email")) { (validator: NGRPropertyValidator!) in validator.required().msgNil(LocalizedString("is required.")) validator.syntax(NGRSyntax.Email).msgWrongSyntax(NGRSyntax.Email, LocalizedString("is not valid syntax.")) } case .Password(var value): return NGRValidator.validateValue(value, named: LocalizedString("Password")) { (validator: NGRPropertyValidator!) in validator.required().msgNil(LocalizedString("is required.")) validator.minLength(6).msgTooShort(LocalizedString("is too short.")) }
No inherits 7JFX$POUSPMMFS 7JFX 7JFX.PEFM .PEFMʹ͓͍ͯ "CTUSBDUϨΠϠʔΛઃ͚ͳ͍ʢܧঝ͠ͳ͍ʣ ܧঝ͢Δͱɺɺɺ ϩδοΫڍಈΛཧղʹ͠ʹ͍͘
ڞ௨Խͨ͠ίʔυΛมߋ͠ʹ͍͘ SFGT J04ΞϓϦͷઃܭͰ#BTF7JFX$POUSPMMFSͷΑ͏ͳͷ࡞Γͨ͘ͳ͍CZࠓઌੜ IUUQRJJUBDPNZJNBKPJUFNTFGFCECGE
No inherits %FMFHBUF (FOFSJDT .PEFMΛҙͷ1SPUPDPMΛ४ڌͤͯ͞ (FOFSJDTͰϩδοΫΛڞ௨Խ
"TQFDU ϩΪϯά"OBMZUJDTૹ৴ $MBTT ڞ௨ॲཧΛ$MBTTͱͯ͠Γग़͠ ڞ௨Խ͍ͨ͠ίʔυҎԼͷ׆༻Λݕ౼͢Δ
Qiitaʹॻ͖·ͨ͠ IUUQRJJUBDPNTVTJFZZJUFNTBGCCEGG
Enjoy Sync life.
iOS, Android Rails ΤϯδχΞ ืूத
MVVM .7$%JBHSBN 7JFX$POUSPMMFS 7JFX .PEFM 7JFX$POUSPMMFSJTlEPFTBMMUIFUIJOHTz
MVVM .77.%JBHSBN 7JFX 7JFX$POUSPMMFS 7JFX.PEFM .PEFM 7JFX$POUSPMMFSCFDPNFTNBMMFS 7JFXJTTUBUFMFTT