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

SwiftUIにおける依存性逆転原則の戦略 / A strategy to import Dependency-Inversion-Principle in SwiftUI apps

Elvis Shi
February 24, 2021

SwiftUIにおける依存性逆転原則の戦略 / A strategy to import Dependency-Inversion-Principle in SwiftUI apps

Elvis Shi

February 24, 2021
Tweet

More Decks by Elvis Shi

Other Decks in Programming

Transcript

  1. 4XJGU6*ʹ͓͚Δ
    ґଘੑٯసݪଇͷઓུ
    f o r ୈ ճ ) " , "5" T X J G U

    View Slide

  2. }
    var employedBy = "YUMEMI Inc."
    var job = "iOS Tech Lead"
    var favoriteLanguage = "Swift"
    var twitter = "@lovee"
    var qiita = "lovee"
    var github = "el-hoshino"
    var additionalInfo = """
    ݘ೿ͳͷʹೣΛอޢͯ͠͠·ͬͨ
    """
    final class Me: Developable, Talkable {

    View Slide

  3. View Slide

  4. struct LandmarkList: View {
    @EnvironmentObject var modelData: ModelData
    @State private var showFavoritesOnly = false
    var body: some View {
    // ...
    }
    }
    IUUQTEFWFMPQFSBQQMFDPNUVUPSJBMTTXJGUVJ

    View Slide

  5. final class ModelData: ObservableObject {
    @Published var landmarks: [Landmark] = [
    // ...
    ]
    }
    IUUQTEFWFMPQFSBQQMFDPNUVUPSJBMTTXJGUVJ

    View Slide

  6. LandmarkList͕ɺ
    ۩৅ܕͷModelDataʹґଘ͍ͯ͠Δɻ

    View Slide

  7. ͳͥ۩৅ʹґଘ͢Δͷ͸ѱ͍จ໌͔
    final class SomeComponent {
    var p: Int = 0
    func f() {}
    // ...
    }
    final class SomeObject {
    let component: SomeComponent
    init(component: SomeComponent) {
    self.component = component
    }
    func doSomething() {
    component.f()
    }
    }

    View Slide

  8. ͳͥ۩৅ʹґଘ͢Δͷ͸ѱ͍จ໌͔
    final class SomeComponent {
    var p: Int = 0
    func f() {}
    // ...
    }
    final class SomeObject {
    let component: SomeComponent
    init(component: SomeComponent) {
    self.component = component
    }
    func doSomething() {
    component.f()
    }
    }
    func test_doSomething() {
    final class MockComponent: SomeComponent {
    override init() {
    super.init()
    self.p = 100
    }
    override func f() {}
    }
    let object = SomeObject(component: MockComponent())
    object.doSomething()
    }
    Inheritance from a final class 'SomeComponent'
    Instance method overrides a 'final' instance method

    View Slide

  9. ͳͥ۩৅ʹґଘ͢Δͷ͸ѱ͍จ໌͔
    class SomeComponent {
    var p: Int = 0
    func f() {}
    // ...
    }
    final class SomeObject {
    let component: SomeComponent
    init(component: SomeComponent) {
    self.component = component
    }
    func doSomething() {
    component.f()
    }
    }
    func test_doSomething() {
    final class MockComponent: SomeComponent {
    override init() {
    super.init()
    self.p = 100
    }
    override func f() {}
    }
    let object = SomeObject(component: MockComponent())
    object.doSomething()
    }
    pOBMDMBTTͰએݴͰ͖ͳ͍ͨΊ
    ύϑΥʔϚϯε͕ඍົʹྑ͘ͳ͍

    ܧঝ͕๷͛ͳ͍͔Β
    ࢓૊Έ্(PE$MBTT͕๷͛ͳ͍
    ͦ΋ͦ΋TUSVDUͩͬͨΒͲ͏͢Δʁ
    ։ൃ࣌͸ΦʔόʔϥΠυશ෦Ͱ͖͚ͨͲ
    อक࣌ʹ௥Ճͨ͠΋ͷͷΦʔόʔϥΠυ
    ๨ΕͨΒͲ͏͢Δʁ

    View Slide

  10. ͳͥ۩৅ʹґଘ͢Δͷ͸ѱ͍จ໌͔
    w ෦඼Λஔ͖׵͑ʹ͍͘
    w ςετ͕ॻ͖ʹ͍͘

    View Slide

  11. ͳͥ۩৅ʹґଘ͢Δͷ͸ѱ͍จ໌͔
    class SomeComponent {
    func a() {}
    func b() {}
    }
    final class ObjectA {
    let component: SomeComponent
    init(component: SomeComponent) {
    self.component = component
    }
    func doSomething() {
    component.a()
    }
    }
    final class ObjectB {
    let component: SomeComponent
    init(component: SomeComponent) {
    self.component = component
    }
    func doSomething() {
    component.b()
    }
    }
    0CKFDU"͕ࣗ෼ʹෆඞཁͳ
    4PNF$PNQPOFOUC
    Λ஌ͬͯΔ͠
    0CKFDU#΋ࣗ෼ʹෆඞཁͳ
    4PNF$PNQPOFOUB
    Λ஌͍ͬͯΔ
    4PNF$PNQPOFOUࣗମͷӨڹൣғ͕
    ඇৗʹ޿ͯ͘อकੑ͕Լ͕Δ
    0CKFDU"ͱ0CKFDU#΋
    ࣗ෼ͨͪͲ͜·Ͱ஌Δඞཁ͕͋Δ͔
    Θ͔Γʹ͍͘ͷͰอकੑ͕Լ͕Δ

    View Slide

  12. ͳͥ۩৅ʹґଘ͢Δͷ͸ѱ͍จ໌͔
    class SomeComponent {
    func a() {}
    func b() {}
    }
    final class ObjectA {
    let component: SomeComponent
    func doSomething() {
    component.a()
    }
    }
    final class ObjectB {
    }
    func test_doSomethingA() {
    final class MockComponent: SomeComponent {
    override func a() {
    //
    }
    override func b() {
    XCTFai()
    }
    }
    let object = ObjectA(component: MockComponent())
    object.doSomething()
    }
    B
    ͚ͩΦʔόʔϥΠυ͢Ε͹͍͍͸ͣͳͷʹɺ
    C
    ΋ΦʔόʔϥΠυ͠ͳͪ͘Ό͍͚ͳ͍

    View Slide

  13. ͳͥ۩৅ʹґଘ͢Δͷ͸ѱ͍จ໌͔
    w ෦඼Λஔ͖׵͑ʹ͍͘
    w ςετ͕ॻ͖ʹ͍͘
    w ৘ใΛ஌Γ͗͢Δ
    w Өڹൣғ͕޿͘อकੑ͕௿͍

    View Slide

  14. ґଘؔ܎ٯసͷݪଇ
    w ্ҐϨϕϧͷϞδϡʔϧ͸ԼҐϨϕϧͷϞδϡʔϧʹ
    ґଘ͢΂͖Ͱ͸ͳ͍ɻ
    ྆ํͱ΋ந৅ʹґଘ͢΂͖Ͱ͋Δɻ
    w ந৅͸ৄࡉʢ۩৅ʣʹґଘͯ͠͸ͳΒͳ͍ɻ
    ৄࡉʢ۩৅ʣ͕ந৅ʹґଘ͢΂͖Ͱ͋Δɻ

    View Slide

  15. ۩৅ʁ
    ந৅ʁ

    View Slide

  16. ந৅
    protocol SomeProtocol {
    func add1(int: Int) -> Int
    }
    ۩৅
    final class A: SomeProtocol {
    func add1(int: Int) -> Int {
    return int + 1
    }
    }
    struct B: SomeProtocol {
    func add1(int: Int) -> Int {
    return int * 2
    }
    }
    enum C: SomeProtocol {
    func add1(int: Int) -> Int {
    return 0
    }
    }

    View Slide

  17. Ԡ༻ͯ͠ΈΔͱ
    protocol ComponentProtocol {
    func f()
    }
    final class SomeObject {
    let component: ComponentProtocol
    init(component: ComponentProtocol) {
    self.component = component
    }
    func doSomething() {
    component.f()
    }
    }
    final class SomeComponent {
    var p: Int = 0
    }
    extension SomeComponent: ComponentProtocol {
    func f() {}
    }

    View Slide

  18. 4XJGU6*ʹద༻ͯ͠ΈΔͱ
    protocol SomeObject: ObservableObject {
    var int: Int { get }
    }
    struct SomeView: View {
    @StateObject var object: SomeObject
    var body: some View {
    Text("\(object.int)")
    }
    }
    final class Counter: SomeObject {
    @Published var int = 0
    }

    View Slide

  19. ͜ͷΑ͏ʹ4XJGU6*Ͱ΋
    ґଘؔ܎Λٯసͯ͠
    อकੑΛߴΊ͍ͯ͜͏

    View Slide

  20. ͜ͷΑ͏ʹ4XJGU6*Ͱ΋
    ґଘؔ܎Λٯసͯ͠
    อकੑΛߴΊ͍ͯ͜͏
    ࢒೦ͳ͕Β
    ͜ΕͰऴΘΓʹͰ͖ͳ͔ͬͨ

    View Slide

  21. 4XJGU6*ʹద༻ͯ͠ΈΔͱ
    protocol SomeObject: ObservableObject {
    var int: Int { get }
    }
    struct MyView: View {
    @StateObject var object: SomeObject
    var body: some View {
    Text("\(object.int)")
    }
    }
    final class Counter: SomeObject {
    @Published var int = 0
    }
    Property type 'SomeObject' does not ...

    View Slide

  22. 4XJGU6*ʹద༻ͯ͠ΈΔͱ
    protocol SomeObject: ObservableObject {
    var int: Int { get }
    }
    struct MyView: View {
    @StateObject var object: SomeObject
    var body: some View {
    Text("\(object.int)")
    }
    }
    final class Counter: SomeObject {
    @Published var int = 0
    }
    Property type 'SomeObject' does not match
    that of the 'wrappedValue' property of its
    wrapper type 'StateObject'

    View Slide

  23. ͳͥ@StateObjectͰprotocol͕
    ͦͷ··࢖͑ͳ͍ͷ͔
    w StateObjectͷwrappedValue: ObjectTypeϓϩύςΟʔ
    ʹΑͬͯɺ಺แ͢Δܕ͕ObjectTypeͷδΣωϦοΫܕͱͯ͠
    ࢖͑Δඞཁ͕͋Δ
    w ObservableObjectʹ͸ObjectWillChangePublisherͱ
    ͍͏ؔ࿈ܕ͕એݴ͞Ε͍ͯΔͨΊɺprotocolͷଘࡏܕͱͯ͠
    δΣωϦοΫܕʹͳΕͳ͍
    w @StateObjectʹݶΒͣɺ@EnvironmentObject΍
    @ObservedObject΋ಉ͡

    View Slide

  24. ͑ʁ͡Ό͋4XJGU6*Ͱ
    ґଘٯసͰ͖ͳ͍Μ͡ΌͶʁ

    View Slide

  25. ͑ʁ͡Ό͋4XJGU6*Ͱ
    ґଘٯసͰ͖ͳ͍Μ͡ΌͶʁ
    ΋ͪΖΜͰ͖·͢Αʂ

    View Slide

  26. ํ๏ɿδΣωϦΫεΛ࢖͏
    protocol SomeObject: ObservableObject {
    var int: Int { get }
    }
    struct MyView: View {
    @StateObject var object: SomeObject
    var body: some View {
    Text("\(object.int)")
    }
    }
    final class Counter: SomeObject {
    @Published var int = 0
    }
    Property type 'SomeObject' does not ...

    View Slide

  27. ํ๏ɿδΣωϦΫεΛ࢖͏
    protocol SomeObject: ObservableObject {
    var int: Int { get }
    }
    struct MyView: View {
    @StateObject var object: Object
    var body: some View {
    Text("\(object.int)")
    }
    }
    final class Counter: SomeObject {
    @Published var int = 0
    }
    δΣωϦΫεΛೖΕͯ͋͛Ε͹
    4UBUF0CKFDUʹ୅ೖͰ͖Δ

    View Slide

  28. ํ๏ɿδΣωϦΫεΛ࢖͏
    protocol SomeObject: ObservableObject {
    var int: Int { get }
    }
    struct MyView: View {
    @StateObject var object: Object
    var body: some View {
    Text("\(object.int)")
    }
    }
    final class Counter: SomeObject {
    @Published var int = 0
    }
    return MyView(object: Counter())

    View Slide

  29. ํ๏ɿδΣωϦΫεΛ࢖͏
    protocol SomeObject: ObservableObject {
    var int: Int { get }
    }
    struct MyView: View {
    @EnvironmentObject var object: Object
    var body: some View {
    Text("\(object.int)")
    }
    }
    final class Counter: SomeObject {
    @Published var int = 0
    }
    return MyView()
    4UBUF0CKFDU΍0CTFSWFE0CKFDUͷ
    ৔߹͸໰୊ͳ͍͕ɺ
    &OWJSPONFOU0CKFDUͷ৔߹͸
    ΠχγϟϥΠβͰδΣωϦΫε͕
    ղܾͰ͖ͳ͍ͷͰɺ
    ໌ࣔతʹδΣωϦοΫܕΛ
    ॻ͘ඞཁ͕͋Δ

    View Slide

  30. ํ๏ɿδΣωϦΫεΛ࢖͏
    protocol ObjectA: ObservableObject {
    var int: Int { get }
    }
    protocol ObjectB: ObservableObject {
    var string: String { get }
    }
    struct MyView: View {
    @EnvironmentObject var a: A
    @EnvironmentObject var b: B
    var body: some View {
    Text("\(a.int), \(b.string)")
    }
    }
    final class MyObjectA: ObjectA {
    @Published var int = 0
    }
    final class MyObjectB: ObjectB {
    @Published var string = ""
    }
    return MyView()
    &OWJSPONFOU0CKFDUͷґଘ͕૿͑Δͱ
    δΣωϦοΫܕ΋૿͍͑ͯ͘

    View Slide

  31. ํ๏ɿδΣωϦΫεΛ࢖͏
    protocol ObjectA: ObservableObject {
    var int: Int { get }
    }
    protocol ObjectB: ObservableObject {
    var string: String { get }
    }
    struct MyView: View {
    @EnvironmentObject var a: A
    @EnvironmentObject var b: B
    var body: some View {
    Text("\(a.int), \(b.string)")
    }
    }
    protocol ComponentA {}
    final class MyObjectA: ObjectA {
    @ObservedObject var a: A
    @Published var int = 0
    }
    protocol ComponentB {}
    final class MyObjectB: ObjectB {
    @ObservedObject var b: B
    @Published var string = ""
    }
    struct Component: ComponentA, ComponentB {}
    return MyView, MyObjectB>()
    &OWJSPONFOU0CKFDUͷґଘʹ΋
    δΣωϦΫε͕࢖ΘΕΔͱ
    δΣωϦοΫܕͷωετͰ͞ΒʹΧΦε

    View Slide

  32. ͦ΋ͦ΋@EnvironmentObject͸
    ۩৅ΛӅ͢ͷʹ࢖͍͍ͨ͜ͱ΋ଟ͍ͷʹ
    δΣωϦΫεͰ۩৅ॻ͔͞ΕͨΒ
    ຊ຤స౗͡ΌΜʁ

    View Slide

  33. ํ๏ɿܕফڈΛ࢖͏
    w ܕফڈ͸ɺԿ͔ܕʹؚ·ΕΔܕม਺Λফڈ͢Δख๏
    w 4XJGU6*΍$PNCJOF͚ͩͰͳ͘ɺ4XJGUϞδϡʔϧ
    ࣗ਎Ͱ΋ྑ͘ݟ͔͚ΒΕΔ
    w 4XJGU6*ɿAnyView
    w $PNCJOFɿAnyPublisherɺAnySubscriber
    w 4XJGUɿAnySequenceɺAnyCollectionʜ

    View Slide

  34. ํ๏ɿܕফڈΛ࢖͏
    protocol SomeObject: ObservableObject {
    var int: Int { get }
    }
    struct MyView: View {
    @EnvironmentObject var object: Object
    var body: some View {
    Text("\(object.int)")
    }
    }
    final class Counter: SomeObject {
    @Published var int = 0
    }
    return MyView()

    View Slide

  35. ํ๏ɿܕফڈΛ࢖͏
    protocol SomeObject: ObservableObject {
    var int: Int { get }
    }
    struct MyView: View {
    @EnvironmentObject var object: AnySomeObject
    var body: some View {
    Text("\(object.int)")
    }
    }
    final class Counter: SomeObject {
    @Published var int = 0
    }
    return MyView() δΣωϦοΫܕ͕ͳ͘ͳͬͯεοΩϦ
    ܕফڈͨ͠ܕΛ࢖͏ͷͰ
    ۩৅Λ஌Δඞཁ͕ͳ͍··

    View Slide

  36. ͰɺͲ͏΍ͬͯܕফڈͷܕΛ
    ࡞Δͷʁ

    View Slide

  37. IUUQTRJJUBDPNPNPDIJNFUBSVJUFNTECFCFG

    View Slide

  38. ํ๏ɿܕফڈΛ࢖͏
    protocol SomeObject: ObservableObject {
    var int: Int { get }
    }
    private class SomeObjectBox {
    var int: Int { fatalError() }
    var objectWillChange: ObservableObjectPublisher { fatalError() }
    }
    4UFQ
    ܧঝͤ͞ΔͨΊͷμϛʔΫϥεΛ࡞Δɻ
    ͦͷࡍΦʔόʔϥΠυͤ͞ΔͨΊͷϓϩύςΟʔ
    ΍ϝιουΛ࡞ΓɺGBUBM&SSPS
    Λฦ͢ɻ
    ·ͨ͜͜Ͱফڈ͍ͨ͠ܕ΋༧Ίఆ͓ٛͯ͘͠ʢࠓ
    ճͷ৔߹͸PCKFDU8JMM$IBOHFͷܕͰ͋Δ
    0CTFSWBCMF0CKFDU0CKFDU8JMM$IBOHF1VCMJTIFS
    Λ0CTFSWBCMF0CKFDU1VCMJTIFSʹݻఆ͢Δʣ

    View Slide

  39. ํ๏ɿܕফڈΛ࢖͏
    private final class AnySomeObjectBox: SomeObjectBox
    where Object.ObjectWillChangePublisher == ObservableObjectPublisher {
    private let object: Object
    fileprivate init(_ object: Object) {
    self.object = object
    }
    override var int: Int {
    return object.int
    }
    override var objectWillChange: ObservableObjectPublisher {
    return object.objectWillChange
    }
    }
    4UFQ
    μϛʔΫϥεΛܧঝͤ͞ɺܧঝͨ͠ࢠΫϥεʹ
    QSPUPDPM४ڌͨ͠δΣωϦοΫܕΛೖΕɺফڈ
    ͍ͨ͠ܕͷ੍ݶΛೖΕ͓ͯ͘ɻ
    ͦͷࡍɺμϛʔΫϥεͷશͯͷϓϩύςΟʔͱ
    ϝιουΛΦʔόʔϥΠυ͠ɺೖΕͬࢠͷ֘౰
    ϓϩύςΟʔ΍ϝιουΛฦ͢ɻ

    View Slide

  40. ํ๏ɿܕফڈΛ࢖͏
    final class AnySomeObject: SomeObject {
    private let object: SomeObjectBox
    fileprivate init(_ object: O)
    where O.ObjectWillChangePublisher == ObservableObjectPublisher {
    self.object = AnySomeObjectBox(object)
    }
    var int: Int {
    return object.int
    }
    var objectWillChange:
    ObservableObjectPublisher {
    return object.objectWillChange
    }
    }
    4UFQ
    QSPUPDPMʹ४ڌͨ͠ܕফڈίϯςφΫϥεΛ࡞
    Γɺ಺෦ͰμϛʔΫϥεΛ࣋ͨͤΔɻ
    ࣮ࡍͷੜ੒࣌͸δΣωϦοΫܕΛ࣋ͬͨࢠΫϥε
    Ͱੜ੒ͯ࣋ͨͤ͠Δɻ
    ͦͯ͠QSPUPDPMͷ֤ϓϩύςΟʔ΍ϝιουΛ
    ೖΕͬ͜ͷͰฦ͢ɻ
    ͜͏͢Ε͹δΣωϦοΫܕΛ࣋ͨͳ͍μϛʔΫϥ
    ε͔͠ͳ͍ͷͰδΣωϦΫε͕ඞཁͳ͍ʀͰ΋Ϋ
    ϥεͷܧঝͱΦʔόʔϥΠυʹΑΓͪΌΜͱδΣ
    ωϦοΫܕΛ࣋ͬͨࢠΫϥεͷৼΔ෣͍Λ͢Δɻ

    View Slide

  41. ํ๏ɿܕফڈΛ࢖͏
    extension SomeObject
    where Self.ObjectWillChangePublisher == ObservableObjectPublisher {
    func eraseToAnySomeObject() -> AnySomeObject {
    return AnySomeObject(self)
    }
    } 4UFQ
    ར༻࣌ͷ࢖͍΍͢͞Λߟ͑ͯɺܕফڈͨ͠ίϯ
    ςφΫϥε΁ͷੜ੒ϝιουΛ࡞Δɻ

    View Slide

  42. ํ๏ɿܕফڈΛ࢖͏
    protocol SomeObject: ObservableObject {
    var int: Int { get }
    }
    struct MyView: View {
    @EnvironmentObject var object: AnySomeObject
    var body: some View {
    Text("\(object.int)")
    }
    }
    final class Counter: SomeObject {
    @Published var int = 0
    }
    return MyView()

    View Slide

  43. ํ๏ɿܕফڈΛ࢖͏
    protocol SomeObject: ObservableObject {
    var int: Int { get }
    }
    struct MyView: View {
    @EnvironmentObject var object: AnySomeObject
    var body: some View {
    Text("\(object.int)")
    }
    }
    final class Counter: SomeObject {
    @Published var int = 0
    }
    return MyView()
    .environmentObject(Counter().eraseToAnySomeObject())
    &OWJSPONFOU0CKFDUͳͷͰɺ
    Ͳ͜Ͱ஫ೖͯ͠΋େৎ෉ʂ

    View Slide

  44. ·ͱΊ
    w ॊೈͰςελϒϧͳઃܭʹґଘؔ܎ٯసͷݪଇ͸ඞਢ
    w 4XJGU6*Ͱ͸ґଘؔ܎ٯస͢Δͷʹ͸ͪΐͬͱେม
    w δΣωϦΫε΍ɺܕফڈͳͲͷख๏͕࢖͑Δ

    View Slide

  45. δΣωϦΫε ܕফڈ
    ϝϦοτ
    ໘౗ͳ࢓૊͕ཁΒͳ͍ͷͰ
    ࣮૷ָ͕
    ґଘͷ࣮ଶΛؾʹ͠ͳͯ͘
    ࡁΉͷͰɺδΣωϦΫε͕
    ཁΒͳ͍͔Βίʔυࣗମ͕
    εοΩϦ͢Δ
    σϝϦοτ
    ґଘ͕૿͑ͨΓɺґଘࣗମ
    ΋ߋʹδΣωϦΫεΛ࢖͏
    ͱॻ͖΋ಡΈ΋͠ʹ͍͘
    ܕফڈͷ࢓૊ࣗମΛ࡞Δͷ
    ͕ඇৗʹ໘౗
    ࢖͏৔໘
    w 4UBUF0CKFDU΍
    0CTFSWFE0CKFDU
    w ґଘ͕গͳ͍
    w δΣωϦΫεͷωετ͕
    ਂ͘ͳ͍
    w &OWJSPONFOU0CKFDUͷ
    ґଘ͕ଟ͍
    w &OWJSPONFOU0CKFDUͷ
    ґଘͷδΣωϦΫεͷω
    ετ͕ਂ͍

    View Slide

  46. ͜ͷΑ͏ʹ4XJGU6*Ͱ΋
    ґଘؔ܎Λٯసͯ͠
    อकੑΛߴΊ͍ͯ͜͏

    View Slide

  47. IUUQTZVNFNJDPOOQBTTDPNFWFOU
    ొஃ͓଴͓ͪͯ͠Γ·͢ʂ

    View Slide