Save 37% off PRO during our Black Friday Sale! »

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

Af64bc38c0ffcfcabdf430759ee491ce?s=47 Elvis Shi
February 24, 2021

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

Af64bc38c0ffcfcabdf430759ee491ce?s=128

Elvis Shi

February 24, 2021
Tweet

Transcript

  1. 4XJGU6*ʹ͓͚Δ ґଘੑٯసݪଇͷઓུ f o r  ୈ   ճ

     ) " , "5"  T X J G U
  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 {
  3. None
  4. struct LandmarkList: View { @EnvironmentObject var modelData: ModelData @State private

    var showFavoritesOnly = false var body: some View { // ... } } IUUQTEFWFMPQFSBQQMFDPNUVUPSJBMTTXJGUVJ
  5. final class ModelData: ObservableObject { @Published var landmarks: [Landmark] =

    [ // ... ] } IUUQTEFWFMPQFSBQQMFDPNUVUPSJBMTTXJGUVJ
  6. LandmarkList͕ɺ ۩৅ܕͷModelDataʹґଘ͍ͯ͠Δɻ

  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() } }
  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
  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ͩͬͨΒͲ͏͢Δʁ ։ൃ࣌͸ΦʔόʔϥΠυશ෦Ͱ͖͚ͨͲ อक࣌ʹ௥Ճͨ͠΋ͷͷΦʔόʔϥΠυ ๨ΕͨΒͲ͏͢Δʁ
  10. ͳͥ۩৅ʹґଘ͢Δͷ͸ѱ͍จ໌͔ w ෦඼Λஔ͖׵͑ʹ͍͘ w ςετ͕ॻ͖ʹ͍͘

  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#΋ ࣗ෼ͨͪͲ͜·Ͱ஌Δඞཁ͕͋Δ͔ Θ͔Γʹ͍͘ͷͰอकੑ͕Լ͕Δ
  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 ΋ΦʔόʔϥΠυ͠ͳͪ͘Ό͍͚ͳ͍
  13. ͳͥ۩৅ʹґଘ͢Δͷ͸ѱ͍จ໌͔ w ෦඼Λஔ͖׵͑ʹ͍͘ w ςετ͕ॻ͖ʹ͍͘ w ৘ใΛ஌Γ͗͢Δ w Өڹൣғ͕޿͘อकੑ͕௿͍

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

  15. ۩৅ʁ ந৅ʁ

  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 } }
  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() {} }
  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 }
  19. ͜ͷΑ͏ʹ4XJGU6*Ͱ΋ ґଘؔ܎Λٯసͯ͠ อकੑΛߴΊ͍ͯ͜͏

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

  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 ...
  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'
  23. ͳͥ@StateObjectͰprotocol͕ ͦͷ··࢖͑ͳ͍ͷ͔ w StateObjectͷwrappedValue: ObjectTypeϓϩύςΟʔ ʹΑͬͯɺ಺แ͢Δܕ͕ObjectTypeͷδΣωϦοΫܕͱͯ͠ ࢖͑Δඞཁ͕͋Δ w ObservableObjectʹ͸ObjectWillChangePublisherͱ ͍͏ؔ࿈ܕ͕એݴ͞Ε͍ͯΔͨΊɺprotocolͷଘࡏܕͱͯ͠

    δΣωϦοΫܕʹͳΕͳ͍ w @StateObjectʹݶΒͣɺ@EnvironmentObject΍ @ObservedObject΋ಉ͡
  24. ͑ʁ͡Ό͋4XJGU6*Ͱ ґଘٯసͰ͖ͳ͍Μ͡ΌͶʁ

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

  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 ...
  27. ํ๏ɿδΣωϦΫεΛ࢖͏ protocol SomeObject: ObservableObject { var int: Int { get

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

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

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

    } } protocol ObjectB: ObservableObject { var string: String { get } } struct MyView<A: ObjectA, B: ObjectB>: 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<MyObjectA, MyObjectB>() &OWJSPONFOU0CKFDUͷґଘ͕૿͑Δͱ δΣωϦοΫܕ΋૿͍͑ͯ͘
  31. ํ๏ɿδΣωϦΫεΛ࢖͏ protocol ObjectA: ObservableObject { var int: Int { get

    } } protocol ObjectB: ObservableObject { var string: String { get } } struct MyView<A: ObjectA, B: ObjectB>: View { @EnvironmentObject var a: A @EnvironmentObject var b: B var body: some View { Text("\(a.int), \(b.string)") } } protocol ComponentA {} final class MyObjectA<A: ComponentA>: ObjectA { @ObservedObject var a: A @Published var int = 0 } protocol ComponentB {} final class MyObjectB<B: ComponentB>: ObjectB { @ObservedObject var b: B @Published var string = "" } struct Component: ComponentA, ComponentB {} return MyView<MyObjectA<Component>, MyObjectB<Component>>() &OWJSPONFOU0CKFDUͷґଘʹ΋ δΣωϦΫε͕࢖ΘΕΔͱ δΣωϦοΫܕͷωετͰ͞ΒʹΧΦε
  32. ͦ΋ͦ΋@EnvironmentObject͸ ۩৅ΛӅ͢ͷʹ࢖͍͍ͨ͜ͱ΋ଟ͍ͷʹ δΣωϦΫεͰ۩৅ॻ͔͞ΕͨΒ ຊ຤స౗͡ΌΜʁ

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

    w 4XJGUɿAnySequenceɺAnyCollectionʜ
  34. ํ๏ɿܕফڈΛ࢖͏ protocol SomeObject: ObservableObject { var int: Int { get

    } } struct MyView<Object: SomeObject>: View { @EnvironmentObject var object: Object var body: some View { Text("\(object.int)") } } final class Counter: SomeObject { @Published var int = 0 } return MyView<Counter>()
  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() δΣωϦοΫܕ͕ͳ͘ͳͬͯεοΩϦ ܕফڈͨ͠ܕΛ࢖͏ͷͰ ۩৅Λ஌Δඞཁ͕ͳ͍··
  36. ͰɺͲ͏΍ͬͯܕফڈͷܕΛ ࡞Δͷʁ

  37. IUUQTRJJUBDPNPNPDIJNFUBSVJUFNTECFCFG

  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ʹݻఆ͢Δʣ
  39. ํ๏ɿܕফڈΛ࢖͏ private final class AnySomeObjectBox<Object: SomeObject>: 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४ڌͨ͠δΣωϦοΫܕΛೖΕɺফڈ ͍ͨ͠ܕͷ੍ݶΛೖΕ͓ͯ͘ɻ ͦͷࡍɺμϛʔΫϥεͷશͯͷϓϩύςΟʔͱ ϝιουΛΦʔόʔϥΠυ͠ɺೖΕͬࢠͷ֘౰ ϓϩύςΟʔ΍ϝιουΛฦ͢ɻ
  40. ํ๏ɿܕফڈΛ࢖͏ final class AnySomeObject: SomeObject { private let object: SomeObjectBox

    fileprivate init<O: SomeObject>(_ 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ͷ֤ϓϩύςΟʔ΍ϝιουΛ ೖΕͬ͜ͷͰฦ͢ɻ ͜͏͢Ε͹δΣωϦοΫܕΛ࣋ͨͳ͍μϛʔΫϥ ε͔͠ͳ͍ͷͰδΣωϦΫε͕ඞཁͳ͍ʀͰ΋Ϋ ϥεͷܧঝͱΦʔόʔϥΠυʹΑΓͪΌΜͱδΣ ωϦοΫܕΛ࣋ͬͨࢠΫϥεͷৼΔ෣͍Λ͢Δɻ
  41. ํ๏ɿܕফڈΛ࢖͏ extension SomeObject where Self.ObjectWillChangePublisher == ObservableObjectPublisher { func eraseToAnySomeObject()

    -> AnySomeObject { return AnySomeObject(self) } } 4UFQ ར༻࣌ͷ࢖͍΍͢͞Λߟ͑ͯɺܕফڈͨ͠ίϯ ςφΫϥε΁ͷੜ੒ϝιουΛ࡞Δɻ
  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()
  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ͳͷͰɺ Ͳ͜Ͱ஫ೖͯ͠΋େৎ෉ʂ
  44. ·ͱΊ w ॊೈͰςελϒϧͳઃܭʹґଘؔ܎ٯసͷݪଇ͸ඞਢ w 4XJGU6*Ͱ͸ґଘؔ܎ٯస͢Δͷʹ͸ͪΐͬͱେม w δΣωϦΫε΍ɺܕফڈͳͲͷख๏͕࢖͑Δ

  45. δΣωϦΫε ܕফڈ ϝϦοτ ໘౗ͳ࢓૊͕ཁΒͳ͍ͷͰ ࣮૷ָ͕ ґଘͷ࣮ଶΛؾʹ͠ͳͯ͘ ࡁΉͷͰɺδΣωϦΫε͕ ཁΒͳ͍͔Βίʔυࣗମ͕ εοΩϦ͢Δ σϝϦοτ

    ґଘ͕૿͑ͨΓɺґଘࣗମ ΋ߋʹδΣωϦΫεΛ࢖͏ ͱॻ͖΋ಡΈ΋͠ʹ͍͘ ܕফڈͷ࢓૊ࣗମΛ࡞Δͷ ͕ඇৗʹ໘౗ ࢖͏৔໘ w 4UBUF0CKFDU΍ 0CTFSWFE0CKFDU w ґଘ͕গͳ͍ w δΣωϦΫεͷωετ͕ ਂ͘ͳ͍ w &OWJSPONFOU0CKFDUͷ ґଘ͕ଟ͍ w &OWJSPONFOU0CKFDUͷ ґଘͷδΣωϦΫεͷω ετ͕ਂ͍
  46. ͜ͷΑ͏ʹ4XJGU6*Ͱ΋ ґଘؔ܎Λٯసͯ͠ อकੑΛߴΊ͍ͯ͜͏

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