Slide 1

Slide 1 text

MVVMΛϕʔεʹෳࡶͳৼΔ෣͍Λ ͔ͬ͠Γ೺ѲͰ͖ΔΞϓϦ։ൃ yohei SUGIGAMI 10/21 shibuya.swift

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

Wantedly

Slide 4

Slide 4 text

Sync Messenger 8FC %FTLUPQ"QQ "OESPJE BOEJ04 ϏδωείϛϡχέʔγϣϯಛԽϝοηʔδϯάαʔϏε

Slide 5

Slide 5 text

Sync iOS 4XJGU .77. '31 1SPNJTF
 Ϟμϯͳཁૉٕज़

Slide 6

Slide 6 text

Let’s Try TZOD Ұ൪࠷ॳͷݕࡧ݁Ռʹग़Δ͸ͣʂ

Slide 7

Slide 7 text

Projects Summary

Slide 8

Slide 8 text

Service Architecture $MJFOU$SPTT1MBUGPSN 4FSWFS.JDSPTFSWJDFT

Slide 9

Slide 9 text

Team "1* 8FC%FTLUPQ J04 "OESPJE GBTUMBOF

Slide 10

Slide 10 text

iOS Schedule ݄̑ ݄̒ ݄̓ ϓϩτλΠϓ։ൃ 'JSFCBTFબఆ +0*/ ਃ੥ νʔϜ಺4MBDLஔ͖׵͑ ࣾ಺4MBDLஔ͖׵͑ +0*/ ຊ։ൃ ࣾ֎ϢʔβϑΟʔυόοΫ ϲ݄ɺ̒ਓ݄͙Β͍ +0*/

Slide 11

Slide 11 text

High Speed Development 13$MPTFE *TTVFT$MPTFE ݄೔࣌఺ $PNNJU

Slide 12

Slide 12 text

Review ϨϏϡʔΞʔΞαΠϯ 5FTU͕͚ͯ͜Δͷ͸ $JSDMF$*ͷௐࢠ͕ѱ͍ɻɻ

Slide 13

Slide 13 text

Review Ϟνϕʔγϣϯ61Ͱ ੜ࢈ੑ޲্

Slide 14

Slide 14 text

Delivery by fastlane 1VMM 3FR .FSHF .BTUFS %FMJWFSZ
 %FW5FTUFS .BTUFSʹ.FSHF͢Δ౓ʹλΠϜϦʔͳΞϓϦ഑෍ ͡Ζ͏͘Μۘ੡ͷGBTUpMF

Slide 15

Slide 15 text

Dog feeding ։ൃ̎िؒͰ4MBDL͔Β৐Γ׵͑Δ ։ൃऀͰ͋Δࣗ෼͔ͨͪΒ·ͣϑΝϯʹͳΔ ։ൃ̍ϲ݄Ͱࣾ಺Ͱ࢖ͬͯ΋Β͍ϑΟʔυόοΫΛ΋Β͏
 ։ൃ̎ϲ݄Ͱ਎ۙͳࣾ֎ͷํʑʹ΋࢖ͬͯ΋Β͍͞Βʹ
 ଟ͘ͷϑΟʔυόοΫΛ௖͖·ͨ͠

Slide 16

Slide 16 text

Very Slow Swift Compile ̍ਓ.BD#PPL1SP̎୆Ͱ։ൃ ίϯύΠϧ଴ͪ࣌ؒΛ༗ޮ׆༻ ܕਪ࿦ͱΫϩδϟʔΩϟϓνϟ͕஗ͦ͏

Slide 17

Slide 17 text

Very Slow Swift Compile .BD#PPL1SP JODI -BUF ͰϑϧίϯύΠϧ࣌ؒ ˠɹ̐෼ඵ
 ίʔυΛݟ௚ͯ͠ίϯύΠϧߴ଎Խ
 ˠɹ෼ඵʢඵʣ ˠɹࠩ෼ίϯύΠϧͷՄೳੑ΋Ξοϓ ܕਪ࿦͠ͳ͍ͰܕΛ໌ه͢Δʢ"SSBZ΍%JDUJPOBSZ΋ʣ ෳ਺ͷTXJGUϑΝΠϧΛҰͭʹ·ͱΊΔ ܧঝ͠ͳ͍DMBTT͸pOBMΛ໌ه͢Δ $BSUBHFରԠͷϥΠϒϥϦ͸1PETͰΠϯετʔϧ͠ͳ͍

Slide 18

Slide 18 text

App Architecture

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

ViewDataBinding 4UBUF 7JFX $POUSPMMFS 7JFX.PEFM %BUB ੹຿ͱؔ৺ࣄͷ෼཭ -PHJD 7JFX 7JFX.PEFMͷ-PHJD͸ 4UBUF %BUBͷมߋ͕੹຿ 7JFXʹ͍ͭͯ͸ؔ஌͠ͳ͍ PS #JOE 7JFX͸4UBUF %BUB͕มߋ
 ͞ΕͨΒಈతʹը໘Λߋ৽ %BUB 4UBUFͷϓϩηε͸ؔ஌͠ͳ͍ 7JFX #JOEJOH .FUIPE$BMM

Slide 21

Slide 21 text

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͸ଞͷϨΠϠʔͷࢀরΛ࣋ͨͳ͍

Slide 22

Slide 22 text

Request Data Flow Sequence 4XJGUZ+40/ 3FBMN 4XJGU5BTL 4XJGU#POE 1FSTJTUFODF 7JFX#JOEJOH "1*

Slide 23

Slide 23 text

Request ViewBinding 4UBUF3FRVFTUJOH 4UBUF&SSPS JUFNT<> 4UBUF/POF *OEJDBUPS7JFX 3FUSZ7JFX /P%BUB7JFX

Slide 24

Slide 24 text

Request ViewBinding 4UBUF3FRVFTUJOH 4UBUF&SSPS JUFNT<JUFN JUFN JUFN ʜ> 4UBUF/POF *OEJDBUPS7JFX 3FUSZ7JFX /P%BUB7JFX

Slide 25

Slide 25 text

Request ViewBinding final class RequestListViewModel { let items: DynamicArray = DynamicArray([]) var requestListState = Dynamic(.None) var noDataFirstViewHidden: Dynamic { let a = indicatorViewHidden.map { $0 == false } let b = requestListFirstState.map { $0 == .Error } return reduce(a, b, c) { $0 || $1 == true } } var indicatorViewHidden: Dynamic { let a = requestListFirstState.map { $0 != RequestListState.Requesting } let b = items.map { count($0) > 0 } return reduce(a, b) { $0 || $1 == true } } var retryViewHidden: Dynamic { let a = requestListFirstState.map { $0 != RequestListState.Error } let b = items.map { count($0) > 0 } return reduce(a, b) { $0 || $1 == true } } 3FRVFTU4UBUFͱ*UFNTͷঢ়ଶͷΑΔڍಈΛએݴ


Slide 26

Slide 26 text

Request ViewBinding final class ContactsViewController: UIViewController { var tableViewDataSourceBond: UITableViewDataSourceBond! 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

Slide 27

Slide 27 text

Request ViewBinding final class RequestListViewModel { typealias RequestTask = Task, NSError> func requestFirst(task: RequestTask) -> RequestTask { self.requestListState.value = .Requesting task.success { [weak self] (collection: ResponseCollection) -> 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Λߋ৽

Slide 28

Slide 28 text

Request ViewBinding final class RequestListViewModel { lazy var stateChangedObserver = Bond { [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

Slide 29

Slide 29 text

Input with Validation ViewBinding &OBCMF4JHO6Q#VUUPO
 7BMJEBUJPO&NBJM1BTTXPSE

Slide 30

Slide 30 text

Validation ViewBinding final class SignUpViewModel { let email = Dynamic("") let password = Dynamic("") var emailError: Dynamic { return email.map { Validator.Email($0).validate() } } var passwordError: Dynamic { return password.map { Validator.Password($0).validate() } } var isSignInInput: Dynamic { let isEmailValid = emailError.map { nil == $0 } let isPasswordValid = passwordError.map { nil == $0 } return reduce(isEmailValid, isPasswordValid) { $0 && $1 == true } } &NBJMͱ1BTTXPSEͷ7BMJEBUJPOʹΑͬͯ 4JHO6Q͕&OBCMFʹͳΔ͔Λએݴ


Slide 31

Slide 31 text

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.")) }

Slide 32

Slide 32 text

No inherits 7JFX$POUSPMMFS 7JFX 7JFX.PEFM .PEFMʹ͓͍ͯ "CTUSBDUϨΠϠʔΛઃ͚ͳ͍ʢܧঝ͠ͳ͍ʣ ܧঝ͢Δͱɺɺɺ ϩδοΫ΍ڍಈΛཧղʹ͠ʹ͍͘ ڞ௨Խͨ͠ίʔυΛมߋ͠ʹ͍͘ SFGT J04ΞϓϦͷઃܭͰ#BTF7JFX$POUSPMMFSͷΑ͏ͳͷ͸࡞Γͨ͘ͳ͍CZࠓ৓ઌੜ
 IUUQRJJUBDPNZJNBKPJUFNTFGFCECGE

Slide 33

Slide 33 text

No inherits %FMFHBUF (FOFSJDT .PEFMΛ೚ҙͷ1SPUPDPMΛ४ڌͤͯ͞
 (FOFSJDTͰϩδοΫΛڞ௨Խ "TQFDU ϩΪϯά΍"OBMZUJDTૹ৴ $MBTT ڞ௨ॲཧΛ$MBTTͱͯ͠੾Γग़͠ ڞ௨Խ͍ͨ͠ίʔυ͸ҎԼͷ׆༻Λݕ౼͢Δ

Slide 34

Slide 34 text

Qiitaʹ΋ॻ͖·ͨ͠ IUUQRJJUBDPNTVTJFZZJUFNTBGCCEGG

Slide 35

Slide 35 text

Enjoy Sync life.

Slide 36

Slide 36 text

iOS, Android Rails ΤϯδχΞ ืूத

Slide 37

Slide 37 text

MVVM .7$%JBHSBN 7JFX$POUSPMMFS 7JFX .PEFM 7JFX$POUSPMMFSJTlEPFTBMMUIFUIJOHTz

Slide 38

Slide 38 text

MVVM .77.%JBHSBN 7JFX 7JFX$POUSPMMFS 7JFX.PEFM .PEFM 7JFX$POUSPMMFSCFDPNFTNBMMFS 7JFXJTTUBUFMFTT