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

SwiftBondとMVVMで 状態管理をシンプルにしよう

SwiftBondとMVVMで 状態管理をシンプルにしよう

SwiftBond, MVVM, Swift, Sync, wantedly, susieyy

yohei sugigami

April 13, 2016
Tweet

More Decks by yohei sugigami

Other Decks in Technology

Transcript

  1. 4XJGU#POEͱ.77.Ͱ
    ঢ়ଶ؅ཧΛγϯϓϧʹ͠Α͏
    yohei SUGIGAMI
    2016 04/13 Reactive Swift Meetup

    View full-size slide

  2. ΞϓϦͷίʔυ͕
    ෳࡶʹͳ͍ͬͯ·ͤΜ͔
    ̎̌̍̑೥݄̑

    4ZODͷ։ൃ࣌ʹ

    ௅ઓͨ͠՝୊

    View full-size slide

  3. എܠ
    Ϣʔβͷମݧʹ͓͚Δظ଴஋ͷߴ౓Խͱଟ༷Խ
    ϞόΠϧ୺຤ͷεϖοΫ޲্

    ௨৴؀ڥͷ޲্
    ΞϓϦͷଟػೳԽ
    ௨৴΍ύϑΥʔϚϯενϡʔχϯάͰඇಉظଟൃ
    ίʔυͷෳࡶԽ

    View full-size slide

  4. 4ZODΞϓϦͷ։ൃͷલʹ
    ࣾ಺։ൃͰ"OHVMBS+4΍ͬͯ·ͨ͠
    .77.ͱ7JFX%BUB#JOEʹ
    ٔΕΔ

    View full-size slide

  5. IUUQTUBSUVQTUPDLQIPUPTDPN
    ͜Εͩʂ

    View full-size slide

  6. MVVM
    IUUQTNTEONJDSPTPGUDPNFOVTMJCSBSZHH W1BOE1
    BTQYTFD
    6*7JFX$POUSPMMFS
    6*#VUUPO
    6*-BCFM
    6*5BCMF7JFX
    FUD
    "1*$MJFOU
    %BUB.PEFM
    1FSTJTUFODF
    6*-PHJD #VTJOFTT-PHJD
    1SFTFOUBUJPO-PHJD
    ෳࡶʹͳΓ͕ͪͳ6*7JFX$POUSPMMFSʹهड़͍ͯͨ͠
    ϩδοΫ͕7JFX.PEFMʹू໿͞ΕΔ
    7JFX.PEFM͸ػೳ͝ͱʹ
    Ϋϥε෼ׂ΍ڞ௨Խ͠΍͍͢
    ςετ͕͠΍͍͢
    ˰ݟ௨͕͠Α͘ͳΔ

    View full-size slide

  7. ੹຿ͱؔ৺ࣄͷ෼཭
    4UBUF
    7JFX
    $POUSPMMFS 7JFX.PEFM
    %BUB
    ؔ஌͍ͯ͠ΔੈքΛখ͘͢͞Δ
    ੹຿Λ໌֬ʹ͢Δ
    ίʔυ͸Θ͔Γ΍͘͢ͳΔ
    -PHJD
    7JFX
    7JFX.PEFM͸
    7JFXʹ͍ͭͯ͸ؔ஌͠ͳ͍
    σʔλͷมߋ͕੹຿
    PS
    #JOE
    7JFX͸σʔλ͕ͲͷΑ͏ʹ
    ࡞ΒΕΔ͔͸ؔ஌͠ͳ͍
    7JFX
    #JOEJOH
    $PNNBOET

    View full-size slide

  8. 4XJGUͰ
    7JFX%BUB#JOEJOH
    Ͳ͏΍Δͷʁ

    View full-size slide

  9. όΠϯσΟϯάϑϨʔϜϫʔΫЋ
    3FBDUJWFͳมԽͷৼΔ෣͍Λؔ਺Ͱ

    هड़Ͱ͖Δ
    4XJGU#POEͱ͸
    ࢖͏͜ͱ͕γϯϓϧͰ͋Δ͜ͱ
    ཧղ͢Δ͜ͱ͕γϯϓϧͰ͋Δ͜ͱ
    ໨ࢦ̎ͭ͢ͷΰʔϧ

    View full-size slide

  10. Ͱ͖Δ͜ͱͷͬ͘͟ΓΠϝʔδਤ

    View full-size slide

  11. IUUQTUBSUVQTUPDLQIPUPTDPN
    ΋͏̍ͭͷෳࡶϙΠϯτ
    ը໘ͷঢ়ଶ؅ཧ

    View full-size slide

  12. 6*4UBDL ͭͷը໘ʹ͸ଟ༷ͳঢ়ଶ͕͋Δʣ
    )PXUPpYBCBEVTFSJOUFSGBDF
    ೔ຊޠ༁όου6*Λվળ͢Δํ๏ʕ6*ͷʮͭͷঢ়ଶʯΛߟ͑Δ
    w ϒϥϯΫঢ়ଶ
    w ௨৴ঢ়ଶ
    w ్தঢ়ଶ
    w Τϥʔঢ়ଶ
    w ཧ૝ঢ়ଶ
    ը໘ͷঢ়ଶΛઃܭɾ؅ཧ͠Α͏
    ΋͏গ͠ଟ͍4UBDL਺ͷ؅ཧΛ
    ޙ΄Ͳ঺հ͠·͢

    View full-size slide

  13. γϯϓϧʹ
    σʔλ͚ͩͰ͸ͳ͘ɺঢ়ଶ͕มԽ͢Δͱ

    ಈతʹը໘ද͕ࣔ

    ੾ΓସΘΔΑ͏ʹ͍ͨ͠
    ଟ༷ͳঢ়ଶͷදࣔΛ੾Γସ͑Δ

    ϩδοΫ͸ෳࡶʹͳΓ͕ͪ
    7JFX4UBUF#JOEJOH

    View full-size slide

  14. ۩ମྫͰݟͯΈΑ͏
    w .77.
    w 7JFX%BUB#JOEJOH
    w 7JFX4UBUF#JOEJOH
    w 4XJGU#POE

    View full-size slide

  15. Α͋͘Δ௨৴ʹΑΔҰཡදࣔ
    JUFNT<JUFN JUFN JUFN ʜ>
    SFRVFTU4UBUF/POFPS3FRVFTUJOHPS&SSPS
    ഑ྻͱ௨৴ͷͭͷঢ়ଶʹண؟͢Δ

    View full-size slide

  16. 4UBUF3FRVFTUJOH 4UBUF&SSPS
    JUFNT<>
    4UBUF/POF
    *OEJDBUPS7JFX 3FUSZ7JFX
    /P%BUB7JFX
    ഑ྻͷΞΠςϜ͕ͳ͍৔߹ͷঢ়ଶ̏ͭ

    View full-size slide

  17. 4UBUF3FRVFTUJOH 4UBUF&SSPS
    JUFNT<JUFN JUFN JUFN ʜ>
    4UBUF/POF
    *OEJDBUPS7JFX 3FUSZ7JFX
    /P%BUB7JFX
    ഑ྻͷΞΠςϜ͕͋Δ৔߹ͷঢ়ଶ̏ͭ
    ΩϟογϡΛදࣔͯ͠Δ৔߹ͳͲ

    View full-size slide

  18. ഑ྻͷঢ়ଶ
    ௨৴ͷঢ়ଶ
    ᶃͭͷঢ়ଶͷมԽ͔Βಈతʹը໘දࣔΛ͍ͨ͠
    ௨৴ͱ഑ྻͷঢ়ଶͷ3FBDUJWFͳมԽΛ
    ؔ਺ͰએݴతʹৼΔ෣͍ʹམͱ͜͠Ή
    ը໘͕ಈతʹܾ·Δ

    View full-size slide

  19. ᶄ഑ྻͷཁૉ͕มԽ͢Δͱಈతʹද͍ࣔͨ͠
    &.15:
    ഑ྻͷঢ়ଶ
    ഑ྻͷཁૉ͕มԽ͢Δͱ
    6*5BCMF7JFXͷද͕ࣔ
    ಈతʹมΘΔ

    View full-size slide

  20. enum RequestState {
    case None
    case Requesting
    case Error
    }
    protocol RequestListStateType {
    associatedtype Item
    var items: ObservableArray { get }

    var requestState: Observable { get }
    var hasVisibleItems: Observable { get }
    var noDataViewHidden: Observable { get }
    var indicatorViewHidden: Observable { get }
    var retryViewHidden: Observable { get }
    }
    Protocol
    0CTFSBCMF 0CTFSBCMF"SSBZܕ͸

    4XJGU#POE͕ఏڙ͢Δػೳ

    View full-size slide

  21. extension RequestListStateType {
    func binding() {
    items
    .map { $0.sequence.count > 0 }
    .bindTo(hasVisibleItems)
    requestState
    .combineLatestWith(hasVisibleItems)
    .map { !($0 == RequestState.Requesting && $1 == false) }
    .bindTo(indicatorViewHidden)
    requestState
    .combineLatestWith(hasVisibleItems)
    .map { !($0 == RequestState.Error && $1 == false) }
    .bindTo(retryViewHidden)
    requestState
    .combineLatestWith(hasVisibleItems)
    .map { !($0 == RequestState.None && $1 == false) }
    .bindTo(noDataViewHidden)
    }
    }
    Protocol Extension
    JUFNTͱSFRVFTU4UBUFͷมԽΛ
    ଞͷ0CTFSBCMFʹ#JOEJOH

    View full-size slide

  22. protocol RequestListType: RequestListStateType {}
    extension RequestListType {
    func request(task: Task>) {

    requestState.value = .Requesting
    task.success { (collection: Collection) in
    self.requestState.value = .None
    self.items.array = collection.items
    }.failure { _ in
    self.requestState.value = .Error
    }
    }
    }
    Protocol Extension
    3FRVFTUͷ1SPNJTF͕׬ྃͨ͠Β

    JUFNTͱSFRVFTU4UBUFʹ஋Λ୅ೖ˰ը໘͕ಈతʹมԽ
    4XJGU#POE͸1SPNJTFΛఏڙ͍ͯ͠ͳ͍ͷͰखಈͰ݁߹

    View full-size slide

  23. struct RequestListViewModel: RequestListType {
    typealias Item = T
    let requestState = Observable(.None)
    let items = ObservableArray([])

    let hasVisibleItems = Observable(false)
    let indicatorViewHidden = Observable(true)
    let retryViewHidden = Observable(true)
    let noDataViewHidden = Observable(true)
    init() {
    binding()
    }
    }
    Protocol Implement
    (FOFSJDTͰ*UFNͷܕΛղܾ
    7JFX.PEFMͷ࣮૷͸༻్ʹΑͬͯ࢖͍෼͚͍ͨͱ͖͕͋ΔͷͰ
    1SPUPDPM&YUFOTJPOΛ׆༻ͯ͠ڞ௨Խ

    View full-size slide

  24. 7JFX$POUSPMMFS

    View full-size slide

  25. let viewModel = RequestListViewModel()
    override func viewDidLoad() {
    super.viewDidLoad(
    viewModel.indicatorViewHidden.bindTo(indicatorView.bnd_hidden)
    viewModel.retryViewHidden.bindTo(retryView.bnd_hidden)
    viewModel.noDataFirstViewHidden.bindTo(noDataView.bnd_hidden)
    viewModel.requestState.observeNew {
    UIApplication.sharedApplication()
    .networkActivityIndicatorVisible = ($0 == .Requesting)
    if $0 == .Error {

    StatusBarNotification.showWithStatus("Connection failed")
    }
    }
    viewModel.items.lift().bindTo(tableView, proxyDataSource: self) { 

    (indexPath, dataSource, tableView) -> UITableViewCell in
    let vm = dataSource[indexPath.section][indexPath.row]
    let cell = tableView.dequeueReusableCellWithIdentifier(

    ContactCell.identifier,
    forIndexPath: indexPath) as! ContactCell

    cell.configure(vm)
    return cell
    }
    final class ContactsViewController: UITableViewController {

    View full-size slide

  26. viewModel.indicatorViewHidden.bindTo(indicatorView.bnd_hidden)
    viewModel.retryViewHidden.bindTo(retryView.bnd_hidden)
    viewModel.noDataFirstViewHidden.bindTo(noDataView.bnd_hidden)
    viewModel.requestState.observeNew {
    UIApplication.sharedApplication()
    .networkActivityIndicatorVisible = ($0 == .Requesting)

    if $0 == .Error {

    StatusBarNotification.showWithStatus("Connection failed")
    }
    }
    COE@IJEEFO͸4XJGU#POE͕6*7JFXΛ
    FYUFOTJPOͨ͠GVODUJPO
    YYYY7JFX)JEEFOͷ஋͕มԽ͢Δͱ#JOE5Pͨ͠
    7JFXͷIJEEFO͕ಈతʹมΘΔ

    View full-size slide

  27. viewModel.items.lift().bindTo(tableView, proxyDataSource: self) { 

    (indexPath, dataSource, tableView) -> UITableViewCell in
    let vm = dataSource[indexPath.section][indexPath.row]
    let cell = tableView.dequeueReusableCellWithIdentifier(

    ContactCell.identifier,
    forIndexPath: indexPath) as! ContactCell

    cell.configure(vm)
    return cell
    }
    JUFNTͷมԽΛ6*5BCMF7JFXʹ#JOEJOH
    ಺෦తʹ4XJGU#POE͕6*5BCMF7JFX%BUBTPVSDFͷ
    ࣮૷ʹͳΔͷͰɺ%BUBTPVSDFͷهड़͕ෆཁʹ
    Ҏ্͕Α͋͘Δ௨৴ͷྫͷ࣮૷ʹͳΓ·͢

    View full-size slide

  28. ·ͱΊ
    ᶃ ෳࡶʹͳΓ͕ͪͳίʔυΛ୺తͰએݴతʹ

    ৼΔ෣͍Λهड़Ͱ͖Δ

    ˰࢓༷Λཧղ͠΍͍͢
    ᶄ ৼΔ෣͍Λ'31Ͱهड़͍ͯ͠ΔͷͰ෭࡞༻

    Λߟྀ͢Δඞཁ͕ແ͍

    ˰ڍಈΛ೺Ѳ͠΍͍͢
    ᶅ 3FBDUJWF͸೉ͦ͠͏͚ͩͲ4XJGU#POE

    ͸γϯϓϧʹهड़Ͱ͖Δ


    View full-size slide