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
14
5.8k
MVVMをベースに複雑な振る舞いを しっかり把握できるアプリ開発
Realm meetup #6 で発表した
Sync iOS開発の舞台裏についてです
プロジェクトの話や、MVVM、ViewBindingなど多義にわたり解説しています
yohei sugigami
August 26, 2015
Tweet
Share
More Decks by yohei sugigami
See All by yohei sugigami
Snapshot Testing in iOS
susieyy
6
3.1k
Redux with iOS
susieyy
0
1.2k
Why use Redux in iOS
susieyy
5
2.6k
ReduxRxを活用したアプリアーキテクチャ
susieyy
8
2.3k
Redux+Rxを活用したiOSアプリアーキテクチャ
susieyy
10
2k
Swaggerで始めるAPI定義管理とコードジェネレート
susieyy
14
7.4k
開発中のアプリをXcode9 & Swift4に移行しました
susieyy
0
3.6k
Wantedly People ViewModel and Rx
susieyy
7
7k
ReduxDevTools' power to the iOS development
susieyy
0
820
Other Decks in Technology
See All in Technology
Azureの開発で辛いところ
re3turn
0
240
エンジニアリングマネージャー視点での、自律的なスケーリングを実現するFASTという選択肢 / RSGT2025
yoshikiiida
4
3.6k
2025年に挑戦したいこと
molmolken
0
150
2025年の挑戦 コーポレートエンジニアの技術広報/techpr5
nishiuma
0
140
コロプラのオンボーディングを採用から語りたい
colopl
5
940
駆け出しリーダーとしての第一歩〜開発チームとの新しい関わり方〜 / Beginning Journey as Team Leader
kaonavi
0
120
#TRG24 / David Cuartielles / Post Open Source
tarugoconf
0
560
EMConf JP の楽しみ方 / How to enjoy EMConf JP
pauli
2
140
Cloudflareで実現する AIエージェント ワークフロー基盤
kmd09
0
280
I could be Wrong!! - Learning from Agile Experts
kawaguti
PRO
8
3.3k
Git scrapingで始める継続的なデータ追跡 / Git Scraping
ohbarye
5
470
KMP with Crashlytics
sansantech
PRO
0
240
Featured
See All Featured
Optimizing for Happiness
mojombo
376
70k
Speed Design
sergeychernyshev
25
730
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
33
2.7k
Keith and Marios Guide to Fast Websites
keithpitt
410
22k
Scaling GitHub
holman
459
140k
Making the Leap to Tech Lead
cromwellryan
133
9k
Code Review Best Practice
trishagee
65
17k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
127
18k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
356
29k
Automating Front-end Workflow
addyosmani
1366
200k
How GitHub (no longer) Works
holman
312
140k
A better future with KSS
kneath
238
17k
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