Upgrade to Pro — share decks privately, control downloads, hide ads and more …

MVVMをベースに複雑な振る舞いを しっかり把握できるアプリ開発

MVVMをベースに複雑な振る舞いを しっかり把握できるアプリ開発

Realm meetup #6 で発表した
Sync iOS開発の舞台裏についてです
プロジェクトの話や、MVVM、ViewBindingなど多義にわたり解説しています

yohei sugigami

August 26, 2015
Tweet

More Decks by yohei sugigami

Other Decks in Technology

Transcript

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

    View full-size slide

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

    View full-size slide

  3. Sync iOS
    4XJGU .77. '31 1SPNJTF

    Ϟμϯͳཁૉٕज़

    View full-size slide

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

    View full-size slide

  5. Projects Summary

    View full-size slide

  6. Service Architecture
    $MJFOU$SPTT1MBUGPSN
    4FSWFS.JDSPTFSWJDFT

    View full-size slide

  7. Team

    "1*
    8FC%FTLUPQ
    J04
    "OESPJE
    GBTUMBOF

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  12. Delivery by fastlane
    1VMM
    3FR
    .FSHF
    .BTUFS
    %FMJWFSZ

    %FW5FTUFS
    .BTUFSʹ.FSHF͢Δ౓ʹλΠϜϦʔͳΞϓϦ഑෍
    ͡Ζ͏͘Μۘ੡ͷGBTUpMF

    View full-size slide

  13. Dog feeding
    ։ൃ̎िؒͰ4MBDL͔Β৐Γ׵͑Δ
    ։ൃऀͰ͋Δࣗ෼͔ͨͪΒ·ͣϑΝϯʹͳΔ
    ։ൃ̍ϲ݄Ͱࣾ಺Ͱ࢖ͬͯ΋Β͍ϑΟʔυόοΫΛ΋Β͏

    ։ൃ̎ϲ݄Ͱ਎ۙͳࣾ֎ͷํʑʹ΋࢖ͬͯ΋Β͍͞Βʹ

    ଟ͘ͷϑΟʔυόοΫΛ௖͖·ͨ͠

    View full-size slide

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

    View full-size slide

  15. Very Slow Swift Compile
    .BD#PPL1SP JODI -BUF
    ͰϑϧίϯύΠϧ࣌ؒ
    ˠɹ̐෼ඵ

    ίʔυΛݟ௚ͯ͠ίϯύΠϧߴ଎Խ

    ˠɹ෼ඵʢඵʣ
    ˠɹࠩ෼ίϯύΠϧͷՄೳੑ΋Ξοϓ
    ܕਪ࿦͠ͳ͍ͰܕΛ໌ه͢Δʢ"SSBZ΍%JDUJPOBSZ΋ʣ
    ෳ਺ͷTXJGUϑΝΠϧΛҰͭʹ·ͱΊΔ
    ܧঝ͠ͳ͍DMBTT͸pOBMΛ໌ه͢Δ
    $BSUBHFରԠͷϥΠϒϥϦ͸1PETͰΠϯετʔϧ͠ͳ͍

    View full-size slide

  16. App Architecture

    View full-size slide

  17. 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

    View full-size slide

  18. 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  23. 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ͷঢ়ଶͷΑΔڍಈΛએݴ


    View full-size slide

  24. 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

    View full-size slide

  25. 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Λߋ৽

    View full-size slide

  26. 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

    View full-size slide

  27. Input with Validation ViewBinding
    &OBCMF4JHO6Q#VUUPO

    7BMJEBUJPO&NBJM1BTTXPSE

    View full-size slide

  28. 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ʹͳΔ͔Λએݴ


    View full-size slide

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

    View full-size slide

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

    IUUQRJJUBDPNZJNBKPJUFNTFGFCECGE

    View full-size slide

  31. No inherits
    %FMFHBUF
    (FOFSJDT
    .PEFMΛ೚ҙͷ1SPUPDPMΛ४ڌͤͯ͞

    (FOFSJDTͰϩδοΫΛڞ௨Խ
    "TQFDU
    ϩΪϯά΍"OBMZUJDTૹ৴
    $MBTT
    ڞ௨ॲཧΛ$MBTTͱͯ͠੾Γग़͠
    ڞ௨Խ͍ͨ͠ίʔυ͸ҎԼͷ׆༻Λݕ౼͢Δ

    View full-size slide

  32. Qiitaʹ΋ॻ͖·ͨ͠
    IUUQRJJUBDPNTVTJFZZJUFNTBGCCEGG

    View full-size slide

  33. Enjoy Sync life.

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide