$30 off During Our Annual Pro Sale. View Details »

SwiftUIむけに 整理された状態監視 Observation

notoroid
August 26, 2023

SwiftUIむけに 整理された状態監視 Observation

札幌iPhoneアプリ開発懇談会2023年8月セッション資料

話す内容
- Source of truth
- CombineとObservationの記述方法比較
- Observation の狙い
- Observation を実現可能としている技術

話さない内容
- データバインディング(@Binding)
- CombineとObservationの相互運用
- Observationの手動実装

お題目
- SwiftUI 5周年
- 記述方法比較 - CombineとObservation
- Observation の狙い
- まとめ

notoroid

August 26, 2023
Tweet

More Decks by notoroid

Other Decks in Technology

Transcript

  1. 4XJGU6*Ή͚ʹ
    ੔ཧ͞Εͨঢ়ଶ؂ࢹ
    0CTFSWBUJPO
    ೳొཁ !OPUPSPJE
    J1IPOFΞϓϦ։ൃऀ೥݄೔

    View Slide

  2. ࠷ۙͷ׆ಈ
    4XJGU6*Λཧղ͢ΔͨΊʹ4XJGUݴޠͷڍಈʹ͍ͭͯ࿩͢͜ͱ͕ଟ͠
    w Αͤ͋ͭΊ4XJGU4XJGU4QFBLFS%FDL
    w IUUQTTQFBLFSEFDLDPNOPUPSPJEZPTFBUVNFTXJGUTXJGUEPU
    w "QQMF͕
    ΍Γ͍ͨ์୊4XJGU"UUSJCVUFT4QFBLFS%FDL
    w IUUQTTQFBLFSEFDLDPNOPUPSPJEBQQMFHBZBSJUBJGBOHUJTXJGU
    BUUSJCVUFT
    w 4XJGU6*͕؆ܿʹهड़Ͱ͖ΔΑ͏ʹͳΔ΄Ͳ4XJGUݴޠͱัଊػೳ͕ෳࡶԽ
    ͍ͯ͠Δ

    View Slide

  3. ࿩͢಺༰
    w 4PVSDFPGUSVUI
    w $PNCJOFͱ0CTFSWBUJPOͷهड़ํ๏ൺֱ
    w 0CTFSWBUJPOͷૂ͍
    w 0CTFSWBUJPOΛ࣮ݱՄೳͱ͍ͯ͠Δٕज़

    View Slide

  4. ࿩͞ͳ͍಺༰
    w σʔλόΠϯσΟϯά !#JOEJOH

    w $PNCJOFͱ0CTFSWBUJPOͷ૬ޓӡ༻
    w 0CTFSWBUJPOͷखಈ࣮૷

    View Slide

  5. ͓୊໨
    w 4XJGU6*प೥
    w هड़ํ๏ൺֱ$PNCJOFͱ0CTFSWBUJPO
    w 0CTFSWBUJPOͷૂ͍
    w ·ͱΊ

    View Slide

  6. 4XJGU6*प೥

    View Slide

  7. 4XJGU6*प೥
    ϚϧνϓϥοτϑΥʔϜରԠͷ6*ϑϨʔϜϫʔΫ
    w 6*पΓͷॆ࣮ "OJNBUJPO 8JEHFU

    w 4XJGU6*Λࢧ͑Δ࢓૊ΈͰ͋Δঢ়ଶ؂ࢹʹ͍ͭͯେ͖ͳΞοϓσʔτ
    w ʙ$PNCJOFGSBNFXPSLΛ࢖ͬͨঢ়ଶ؂ࢹΛ4XJGU6*ͱ૊Έ߹Θͤ
    w ʙ0CTFSWBUJPOGSBNFXPSLΛ࢖ͬͨঢ়ଶ؂ࢹͱ૊Έ߹ΘͤΔ

    View Slide

  8. 0CTFSWBUJPOGSBNFXPSL
    "QQMFۘ੡σʔλ؍ଌϑϨʔϜϫʔΫ
    w J04Ҏ߱ରԠ
    w ։ൃπʔϧ9DPEFҎ߱
    w $PNCJOFGSBNFXPSL ʙ
    ͷ୅ΘΓʹ4XJGU6*Ͱར༻Ͱ͖Δ
    w 4XJGUιʔείʔυ্ͰJNQPSU0CTFSWBUJPOͰએݴ͢Δ͜ͱͰ࢖༻Մೳ

    View Slide

  9. %JTDPWFS
    0CTFSWBUJPOJO
    4XJGU6*
    IUUQTEFWFMPQFSBQQMFDPN
    XXED

    View Slide

  10. ೔ຊޠʹ༁ͨ͠༗ࢤͷํ
    ʲ88%$ʳ%JTDPWFS0CTFSWBUJPOJO
    4XJGU6*ʢ೔ຊޠ༁ʣ
    IUUQTCMPHDPEFDBOEZDPN
    EJTDPWFS@PCTFSWBUJPO@JO@TXJGUVJ

    View Slide

  11. େ·͔ͳ಺༰
    %JTDPWFS0CTFSWBUJPOJO4XJGU6*

    View Slide

  12. 0CTFSWBUJPO
    GSBNFXPSLͰ͜Ε·
    Ͱͷํ๏ΑΓ΋࢖͍΍
    ͘͢ͳͬͨͥɺώϟο
    ϋʔʂ
    ৽ػೳਪ͠ͰϝϦοτ͕͸͖ͬΓ͠ͳ͍ɻ

    View Slide

  13. ଞͷهࣄ΋ݟͯΈΔ

    View Slide

  14. 88%$ޙͷ#MPHهࣄ
    ͞Α͏ͳΒɺ$PNCJOFɻͦͯ͜͠Μʹͪ͸ɺ0CTFSWBUJPOɻ2JJUB
    IUUQTRJJUBDPNMPWFFJUFNTECCBCCD
    େ·͔ͳ಺༰
    ͜Ε·Ͱͷ࢓૊Έ $PNCJOFGSBNFXPSL
    ͸4XJGU6*͕ඞཁͱͯ͠
    ͍Δঢ়ଶ؂ࢹʹ͸༨ܭͳ΋ͷ͕ଟ͗ͨ͢ɻ
    4XJGU6*ʹඞཁͳঢ়ଶ؂ࢹ͚ͩΛఏڙͨ͠ͷ͕0CTFSWBUJPOͳͷͰ
    ࠓޙ͸0CTFSWBUJPOΛ࢖͍ͬͯ͜͏ɻ

    View Slide

  15. 🤔

    View Slide

  16. ͳͥ0CTFSWBUJPO
    ໌֬ͳར఺΍ࠓޙͷల๬͕ظ଴Ͱ͖ͳ͍ݶΓɺ৽ϑϨʔϜϫʔΫΛ
    ಋೖ͢Δಈػͱͯ͠͸ෆ଍ͯ͠͸͍ͳ͍͔
    ৽͍ٕ͠ज़ΛਐΊΔͳΒ͹ཧ٧Ίͷ෦෼͕΋͏ͪΐͬͱཉ͍͠

    View Slide

  17. 0CTFSWBUJPOʹؔ܎͢Δηογϣϯ
    0CTFSWBUJPOͷઆ໌͸4XJGUͷ৽ػೳ 88%$
    ͷதͰ4XJGUNBDSPT
    ͷઆ໌ʹؚ·Ε͍ͯΔ
    ˠ4XJGU6*͕0CTFSWBUJPOΛಋೖ͢Δར఺ʹ͍ͭͯ͸໌֬ʹ͸ޠΒΕ
    ͍ͯͳ͍ɻ
    IUUQTEFWFMPQFSBQQMFDPNXXED UJNF

    View Slide

  18. $PNCJOFͱ0CTFSWBUJPO
    ৽چҧ͍Λߟ͑Δʹ͸Ͳ͏͢Ε͹ྑ͍͔

    View Slide

  19. ͳΒ͹ಉ͡ػೳΛ
    αϯϓϧίʔυͰ֬ೝͯ͠ΈΑ͏

    View Slide

  20. هड़ํ๏ൺֱ
    $PNCJOFͱ0CTFSWBUJPO

    View Slide

  21. αϯϓϧίʔυ J04ʙ

    0CTFSWBUJPO$PNCJOF$PEF$PNQBSJTPO
    HJUIVCDPNOPUPSPJE
    0CTFSWBUJPO$PNCJOF$PEF$PNQBSJTPO

    View Slide

  22. αϯϓϧίʔυΛݟΔલʹ
    4XJGU6*ͷ͓໿ଋΛ֬ೝ
    HJUIVCDPNOPUPSPJE
    0CTFSWBUJPO$PNCJOF$PEF$PNQBSJTPO

    View Slide

  23. %BUB'MPX
    5ISPVHI4XJGU6*
    88%$
    EFWFMPQFSBQQMFDPN
    WJEFPTQMBZ
    XXED
    UJNF

    View Slide

  24. %BUB'MPX
    5ISPVHI4XJGU6*
    88%$
    EFWFMPQFSBQQMFDPN
    WJEFPTQMBZ
    XXED
    UJNF

    View Slide

  25. 4XJGU6*Ͱͷσʔλهड़Օॴίϯηϓτ

    View Slide

  26. ᶃσʔλιʔεΛ഑ஔ͢Δ7JFX
    ᶄσʔλιʔε
    4XJGU6*Ͱͷσʔλهड़Օॴίϯηϓτ
    ᶅσʔλιʔεΛ؍ଌ͢Δ7JFX

    View Slide

  27. ᶃσʔλιʔεΛ഑ஔ͢Δ7JFX
    ᶄσʔλιʔε͸
    ΦϒδΣΫτதͷଐੑΛ؍ଌͤ͞
    Δɻ
    4XJGU6*Ͱͷσʔλهड़Օॴ
    ᶅσʔλιʔεΛ؍ଌ͢Δ7JFX

    View Slide

  28. ᶃσʔλιʔεΛ഑ஔ͢Δ"QQ
    4XJGU6*୯ମͰΞϓϦΛ࡞੒Ͱ
    ͖ΔΑ͏ʹͳͬͨͨΊ
    ᶄσʔλιʔε͸
    ΦϒδΣΫτதͷଐੑΛ؍ଌͤ͞
    Δɻ
    4XJGU6*Ͱͷσʔλهड़Օॴ
    ᶅσʔλιʔεΛ؍ଌ͢Δ7JFX

    View Slide

  29. 4XJGU6*Ͱ͸؍ଌ͢Δ؍ଌՄೳͳ
    σʔλΦϒδΣΫτͷ
    ࢀরํ๏͕େ·͔ʹͭ

    View Slide

  30. ؍ଌՄೳͳσʔλͷࢀরํ๏
    ύϥϝʔλʔ౉͠ ؀ڥม਺౉͠ 7JFXͷม਺ͱͯ͠࢖͏

    View Slide

  31. ؍ଌՄೳͳσʔλͷࢀরํ๏ύϥϝʔλʔ౉͠

    View Slide

  32. ؍ଌՄೳͳσʔλͷࢀরํ๏؀ڥม਺౉͠

    View Slide

  33. ؍ଌՄೳͳσʔλͷࢀরํ๏7JFXͷม਺ͱͯ͠࢖͏

    View Slide

  34. λάฤूΞϓϦ
    w 5BHͷू߹Λ؅ཧ͢ΔΞϓϦ
    w 5BHʹ͸࿈൪Λ͚ͭΔ
    w 0CTFSWBUJPO$PNCJOFͷಉ͡ί
    ʔυΛهड़
    w ؍ଌ͢ΔΦϒδΣΫτͷ౉͠ํ
    ΋छ༻ҙ
    αϯϓϧίʔυ

    View Slide

  35. B
    λά5BH
    import Foundation


    struct Tag: Identifiable {


    let name: String


    let value: Int


    var id: Int { value }


    }


    extension Array {


    static func defaultTags() -> [Tag] {


    [.init(name: "Tag1", value: 1), .init(name: "Tag2", value: 2), .init(name:
    "Tag3", value: 3)]


    }


    }


    ᶃ*EFOUJ
    fi
    BCMFϓϩτίϧΛࢦఆ
    ᶄ഑ྻॳظԽ༻ͷTUBUJDؔ਺

    View Slide

  36. C
    7JFXͷॲཧ
    struct ContentViewByObservationDependOnViewLifecycle: View {


    var tagCollection: TagCollectionByObservation = .init()


    var body: some View {


    VStack {


    Button {


    let newTagValue = tagCollection.collectionCount + 1


    tagCollection.collection.append(


    .init(name: "Tag\(newTagValue)", value: newTagValue)


    )


    } label: {


    Text("Add")


    }


    Text("Tag number:\(tagCollection.collectionCount)")




    List(tagCollection.collection) { tag in


    Label(


    title: { Text("\(tag.name)") },


    icon: { Image(systemName: "tag.fill") }


    )


    }


    .listStyle(.plain)


    }


    .padding()


    }


    }
    ᶃ7JFXͰ͸એݴʹҧ͍͋Γ
    ᶄϘλϯΛλοϓ͢Δͱ
    5BH͕૿͑Δ͚ͩ

    View Slide

  37. D
    σʔλιʔεΛ഑ஔ͢Δ"QQ
    import SwiftUI


    @main


    struct ObservationCombineCodeComparisonApp: App {


    @StateObject var tagCollectionByCombine: TagCollectionByCombine = .init()


    var tagCollectionByObservation:TagCollectionByObservation = .init()




    var body: some Scene {





    }


    }
    @StateObject var tagCollectionByCombine: TagCollectionByCombine = .init()
    var tagCollectionByObservation:TagCollectionByObservation = .init()
    $PNCJOF
    0CTFSWBUJPO
    ᶃঢ়ଶର৅ʹࢦఆ͢Δ4XJGU"UUSJCVUFT
    ᶄಛʹهड़ͳ͠

    View Slide

  38. E
    σʔλιʔεΦϒδΣΫτ
    $PNCJOF
    0CTFSWBUJPO
    import Foundation


    class TagCollectionByCombine: ObservableObject {


    @Published var collection: [Tag] = .defaultTags()


    var collectionCount: Int { collection.count }


    }
    import Observation


    @Observable class TagCollectionByObservation {


    var collection: [Tag] = .defaultTags()


    var collectionCount: Int { collection.count }


    }


    ᶄ!0CTFSWBCMFΛΫϥεͷલʹ෇͚Δ
    ᶃ0CTFSWBCMF0CKFDUͷ೿ੜΫϥε

    View Slide

  39. F
    ؍ଌՄೳͳσʔλͷ౉͠ํ
    $PNCJOF
    0CTFSWBUJPO
    ContentViewByCombineFromParameter(tagCollection: tagCollectionByCombine)
    ContentViewByCombineFromEmvironment()


    .environmentObject(tagCollectionByCombine)
    ContentViewByObservationFromParameter(tagCollection: tagCollectionByObservation)
    ContentViewByObservationFromEmvironment()


    .environment(tagCollectionByObservation)
    FOWJSPONFOU0CKFDUͱFOWJSPONFOUͷҧ͍

    View Slide

  40. G
    σʔλιʔεΛ؍ଌ͢Δଆͷఆٛ
    $PNCJOF
    0CTFSWBUJPO
    struct ContentViewByCombineFromParameter: View {


    @ObservedObject var tagCollection: TagCollectionByCombine


    }
    struct ContentViewByCombineFromEmvironment: View {


    @EnvironmentObject var tagCollection: TagCollectionByCombine


    }
    struct ContentViewByObservationFromParameter: View {


    var tagCollection: TagCollectionByObservation


    }
    struct ContentViewByObservationFromEmvironment: View {


    @Environment(TagCollectionByObservation.self) var tagCollection:


    }
    ᶃҾ਺Ͱ౉͞ΕΔ৔߹
    ᶄ؀ڥม਺Ͱ౉͞ΕΔ৔߹
    ᶅ؀ڥม਺Ͱ౉͞ΕΔ৔߹ͷΈࢦఆ

    View Slide

  41. struct ContentViewDependOnViewLifecycle: View {


    @StateObject var tagCollection: TagCollectionByCombine = .init()


    }
    H
    7JFXͷม਺ͱͯ͠ಠཱͯ͠࢖͏
    $PNCJOF
    0CTFSWBUJPO
    ᶃม਺ఆٛͰ͸!4UBUF0CKFDU͕ඞཁ
    struct ContentViewByObservationDependOnViewLifecycle: View {


    var tagCollection: TagCollectionByObservation = .init()


    }
    ᶄಛʹࢦఆͳ͠

    View Slide

  42. $PNCJOFͱ0CTFSWBUJPOͷҧ͍
    هड़͢ΔՕॴ͸ͦΕ΄ͲมΘΒͣ
    $PNCJOF 0CTFSWBUJPO
    ϑϨʔϜϫʔΫ 'PVOEBUJPO $PNCJOF͸ඪ४ѻ͍
    0CTFSWBUJPO
    σʔλιʔεͷఆٛ !4UBUF0CKFDU ಛʹͳ͠
    σʔλιʔεͷએݴ
    0CTFSWBCMF0CKFDUͷ೿ੜΫϥε
    ؍ଌ͍ͤͨ͞ଐੑʹ!1VCMJTIFEࢦఆ
    Ϋϥεͷઌ಄ʹ!0CTFSWBCMF
    Կ΋ࢦఆ͠ͳ͍ͱ؍ଌ͢Δଐੑʹ
    σʔλιʔεΛ؍ଌ͢Δ
    ଆͷఆٛ
    !0CTFSWFE0CKFDU
    !&OWJSPONFOU0CKFDU
    ؀ڥม਺Ͱ౉͞ΕΔ৔߹ͷΈ
    !&OWJSPONFOU
    σʔλόΠϯσΟϯά ৗʹ༗ޮ
    !#JOEBCMFΛࢦఆ͠ͳ͍ͱ༗ޮ
    ʹͳΒͳ͍

    View Slide

  43. هड़ํ๏͕एׯมΘ͍ͬͯΔ͕
    ϝϦοτ͸͋Δͷ͔

    View Slide

  44. 0CTFSWBUJPOͷૂ͍

    View Slide

  45. $PNCJOF ʙ
    Ͱ͸γϯϓϧʹ࣮ݱͰ͖ͳ͍͜ͱ͕ଟ͍
    w σʔλ؍ଌͷͨΊʹ4XJGU"UUSJCVUFTΛ෇Ճ͢Δඞཁ͕͋Δ
    w !4UBUF0CKFDUɺ!0CTFSWFE0CKFDUɺ!&OWJSPONFOU0CKFDU
    w ഑ྻ ू߹Λ͞Βʹू߹
    Λͦͷ··σʔλ؍ଌର৅ʹͰ͖ͳ͍
    w 4XJGU6*Ͱ஋ΛऔΓѻ͏4XJGU"UUSJCVUFTͱিಥ͍ͯ͠Δ
    w !4UBUF
    w !"QQ4UPSBHF
    w !4DFOF4UPSFBHF

    View Slide

  46. ഑ྻ ू߹Λ͞Βʹू߹
    Λͦͷ··σʔλ؍ଌର৅ʹ
    $PNCJOF
    0CTFSWBUJPO
    @main


    struct ObservationCombineCodeComparisonApp: App {


    @StateObject var tagCollectionByCombine: [TagCollectionByCombine] = .init()


    }


    ΤϥʔͱͳΔ
    @main


    struct ObservationCombineCodeComparisonApp: App {


    var tagCollectionByObservation:[TagCollectionByObservation] = .init()


    }
    Τϥʔʹ͸ͳΒͣͦΕͧΕ؍
    ଌՄೳͳσʔλΦϒδΣΫτ
    ͱͯ͠ػೳ

    View Slide

  47. 4XJGU6*Ͱ஋ΛऔΓѻ͏4XJGU"UUSJCVUFTͱিಥ͍ͯ͠Δ
    w !4UBUF
    w !"QQ4UPSBHF
    w !4DFOF4UPSFBHF
    ᶃ7JFX಺Ͱঢ়ଶ؂ࢹͰ͖Δม਺ʹࢦఆ
    ᶄΞϓϦͷετϨʔδ͔Βσʔλऔಘ
    ᶅγʔϯͷण໋಺Ͱ༗ޮͳσʔλ͔Βࢀর
    σʔλ؍ଌͷͨΊ͚ͩʹ4XJGU"UUSJCVUFTΛ઎༗͞ΕΔͱଞͷ༻్ͷ
    4XJGU"UUSJCVUFTͷ࢖༻Λ๦͓͛ͯΓճආࡦͷͨΊͷίʔυهड़͕ඞཁ
    import Foundation


    class TagCollectionByCombine: ObservableObject {


    @Published var collection: [Tag] = .defaultTags()


    var collectionCount: Int { collection.count }


    }

    View Slide

  48. 4XJGU"UUSJCVUFTͱ͸Կ
    IUUQTTQFBLFSEFDLDPNOPUPSPJEBQQMFHBZBSJUBJGBOHUJTXJGUBUUSJCVUFT

    View Slide

  49. 0CTFSWBUJPOొ৔ͷഎܠ
    w 4XJGU6*ެ։࣌ ʙ

    w $PNCJOF
    w 4XJGU"UUSJCVUFT
    w Ϋϥε೿ੜ
    w 4XJGU.BDSPT
    ొ৔
    w 0CTFSWBUJPOʹΑΔσʔλ؍ଌͷ࢓૊ΈΛ࣮ݱ
    w ա౓ʹ4XJGU"UUSJCVUFTґଘ͠ͳ͘ͳͬͨ

    View Slide

  50. 0CTFSWBUJPOͷͦͷଞͷಛ௃
    w σʔλόΠϯσΟϯά͸Φϓγϣϯࢦఆ΁
    w $PNCJOFͰ͸σʔλΦϒδΣΫτ͸σϑΥϧτͰ༗ޮ
    w !#JOEJOHΛ࢖͏ͱΦϒδΣΫτʹؚ·ΕΔσʔλΛࢀরɺมߋՄೳʹ
    w 0CTFSWBUJPOͰ͸σʔλΦϒδΣΫτͷࢀরଆͰ!#JOEBCMFࢦఆ͕ඞཁ
    w σʔλΦϒδΣΫτͷଐੑ͸σϑΥϧτͰ؍ଌର৅ʹ
    w !0CTFSWBUJPO*HOPSFEΛࢦఆ͠ͳ͍ݶΓৗʹ؍ଌର৅ѻ͍

    View Slide

  51. ·ͱΊ
    wσʔλ؍ଌͷ࢓૊Έͱͯ͠0CTFSWBUJPO͕ొ৔͠·ͨ͠
    w4XJGU6*޲͚
    w$PNCJOFͱࣅ௨ͬͨهड़͕Ͱ͖Δ͕ผ෺
    w೥4XJGU6*ൃද౰ॳ͔Βͷෆຬ఺ ໃ६఺
    Λվળ

    View Slide

  52. ࢀߟ
    w %JTDPWFS0CTFSWBUJPOJO4XJGU6*88%$
    7JEFPT"QQMF%FWFMPQFS
    w IUUQTEFWFMPQFSBQQMFDPNXXED
    w ʲ88%$ʳ%JTDPWFS0CTFSWBUJPOJO4XJGU6*ʢ೔ຊ
    ޠ༁ʣJ1IPOFΞϓϦ։ൃߨ࠲ʛ$PEF$BOEZΦϯϥΠ
    ϯϓϩάϥϛϯάεΫʔϧ
    w IUUQTCMPHDPEFDBOEZDPN
    EJTDPWFS@PCTFSWBUJPO@JO@TXJGUVJ
    w ͞Α͏ͳΒɺ$PNCJOFɻͦͯ͜͠Μʹͪ͸ɺ
    0CTFSWBUJPOɻ2JJUB
    w IUUQTRJJUBDPNMPWFFJUFNT
    ECCBCCD
    w 8IBU`TOFXJO4XJGU88%$7JEFPT"QQMF
    %FWFMPQFS
    w IUUQTEFWFMPQFSBQQMFDPNXXED
    UJNF
    w %BUB'MPX5ISPVHI4XJGU6*88%$7JEFPT
    "QQMF%FWFMPQFS
    w IUUQTEFWFMPQFSBQQMFDPNWJEFPTQMBZ
    XXED UJNF
    w "QQMF͕
    ΍Γ͍ͨ์୊4XJGU"UUSJCVUFT4QFBLFS
    %FDL
    w IUUQTTQFBLFSEFDLDPNOPUPSPJEBQQMFHB
    ZBSJUBJGBOHUJTXJGUBUUSJCVUFT
    w Αͤ͋ͭΊ4XJGU4XJGU4QFBLFS%FDL
    w IUUQTTQFBLFSEFDLDPNOPUPSPJE
    ZPTFBUVNFTXJGUTXJGUEPU

    View Slide