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
940
Other Decks in Technology
See All in Technology
サイバー捜査員研修(後半)
nomizone
1
830
システム思考で問題に対処する
yussak
0
220
Eight Engineering Unit 紹介資料
sansan33
PRO
3
8.1k
トヨタ⽣産⽅式(TPS)⼊⾨
recruitengineers
PRO
3
690
【AG-UI × A2UI × MCP Apps】Generative UIをやさしく解説する
nrinetcom
PRO
1
100
攻撃と防御で学ぶAI時代のプロダクトセキュリティ演習
recruitengineers
PRO
8
2.4k
取引先から届く 「セキュリティチェックシート」の読み解き方
kamadamakoto
0
140
個人OSSが、机の上から世界に広がるまでの話
shinyasaita
1
380
JavaScript 研修 (2026)
recruitengineers
PRO
2
490
AWS ネットワーク構築でハマった(ハマりかけた) 5選とそこから得た教訓
nagisa53
4
200
メルカリのグローバルアプリで挑んだ AlloyDB 運用と課題解決の実践記
hatappi
0
240
【CEDEC2026】『Relink』を拡張せよ - 『GRANBLUE FANTASY: Relink - Endless Ragnarok』の開発速度と品質を守るCI運用
cygames
PRO
0
140
Featured
See All Featured
DBのスキルで生き残る技術 - AI時代におけるテーブル設計の勘所
soudai
PRO
68
56k
End of SEO as We Know It (SMX Advanced Version)
ipullrank
3
4.3k
The Illustrated Children's Guide to Kubernetes
chrisshort
51
53k
Organizational Design Perspectives: An Ontology of Organizational Design Elements
kimpetersen
PRO
1
790
Utilizing Notion as your number one productivity tool
mfonobong
4
530
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
2.1k
JAMstack: Web Apps at Ludicrous Speed - All Things Open 2022
reverentgeek
1
550
Paper Plane (Part 1)
katiecoart
PRO
1
10k
RailsConf 2023
tenderlove
30
1.5k
Art, The Web, and Tiny UX
lynnandtonic
304
22k
Dominate Local Search Results - an insider guide to GBP, reviews, and Local SEO
greggifford
PRO
0
260
The Curious Case for Waylosing
cassininazir
1
450
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