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.2k
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.5k
開発中のアプリをXcode9 & Swift4に移行しました
susieyy
0
3.7k
Wantedly People ViewModel and Rx
susieyy
7
7.1k
ReduxDevTools' power to the iOS development
susieyy
0
850
Other Decks in Technology
See All in Technology
ElixirがHW化され、最新CPU/GPU/NWを過去のものとする数万倍、高速+超省電力化されたWeb/動画配信/AIが動く日
piacerex
0
110
AIと開発者の共創: エージェント時代におけるAIフレンドリーなDevOpsの実践
bicstone
1
250
Lightdashの利活用状況 ー導入から2年経った現在地_20250409
hirokiigeta
2
270
50人の組織でAIエージェントを使う文化を作るためには / How to Create a Culture of Using AI Agents in a 50-Person Organization
yuitosato
6
3.2k
“パスワードレス認証への道" ユーザー認証の変遷とパスキーの関係
ritou
1
450
Amazon S3 Tables + Amazon Athena / Apache Iceberg
okaru
0
250
技術者はかっこいいものだ!!~キルラキルから学んだエンジニアの生き方~
masakiokuda
2
150
【2025年度新卒技術研修】100分で学ぶ サイバーエージェントのデータベース 活用事例とMySQLパフォーマンス調査
cyberagentdevelopers
PRO
5
6.5k
「それはhowなんよ〜」のガイドライン #orestudy
77web
9
2.4k
Would you THINK such a demonstration interesting ?
shumpei3
1
160
食べログが挑む!飲食店ネット予約システムで自動テスト無双して手動テストゼロを実現する戦略
hagevvashi
1
190
Spring Bootで実装とインフラをこれでもかと分離するための試み
shintanimoto
4
460
Featured
See All Featured
Optimizing for Happiness
mojombo
377
70k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
507
140k
Side Projects
sachag
452
42k
The World Runs on Bad Software
bkeepers
PRO
67
11k
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
5
520
Producing Creativity
orderedlist
PRO
344
40k
BBQ
matthewcrist
88
9.6k
Rails Girls Zürich Keynote
gr2m
94
13k
The Power of CSS Pseudo Elements
geoffreycrofte
75
5.7k
Gamification - CAS2011
davidbonilla
81
5.2k
Building an army of robots
kneath
304
45k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
34
2.2k
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