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

Xcode Previewを気軽に利用するためのDI戦略

matsuji
September 03, 2023

Xcode Previewを気軽に利用するためのDI戦略

Xcode Previewでは静的なUIだけでなく、実際にUIを操作して簡単な動作確認ができます。

Xcode Previewを本格的に使うにはDependency Injection(DI)をする必要がありますが、SwiftUIのViewへのDIはアプリのトップレベルから依存をパスする、所謂バケツリレーが必要で、実装コストが大きくなってしまいます。
一方@Environment(Object)だとバケツリレーせずにDIできますが、コンパイラの力を借りることができず、Xcode Previewへのハードルが上がります。
そこでこのトークでは、Xcode Previewのことを最優先に考え、開発者が「いつでも」「気軽に」「安全に」Xcode Previewを利用できるDI戦略を紹介します。

このトークを通じて、みなさんがもっと気軽にXcode Previewを使い倒せることを目指します。

matsuji

September 03, 2023
Tweet

More Decks by matsuji

Other Decks in Technology

Transcript

  1. 5

  2. struct ItemListView: View { @StateObject let viewModel = ItemListViewModel() var

    body: some View { // viewModelͷitemsΛ࢖ͬͯϦετΛදࣔ͢Δ } } class ItemListViewModel: ObservableObject { @Published var items: [Item] = [] func fetchItems() async throws { items = try await APIClient.share.getItems() } } 7
  3. struct ItemListView: View { @StateObject let viewModel = ItemListViewModel() var

    body: some View { // viewModelͷitemsΛ࢖ͬͯϦετΛදࣔ͢Δ } } class ItemListViewModel: ObservableObject { @Published var items: [Item] = [] func fetchItems() async throws { items = try await APIClient.share.getItems() } } ViewModelΛ StateObjectͰ؅ཧ 7
  4. struct ItemListView: View { @StateObject let viewModel = ItemListViewModel() var

    body: some View { // viewModelͷitemsΛ࢖ͬͯϦετΛදࣔ͢Δ } } class ItemListViewModel: ObservableObject { @Published var items: [Item] = [] func fetchItems() async throws { items = try await APIClient.share.getItems() } } ViewModelΛ StateObjectͰ؅ཧ APIܦ༝ͰitemsΛऔಘ 7
  5. Xcode PreviewΛ࢖͏্Ͱͷ໰୊఺ // ViewModelͷதͷؔ਺ func fetchItems() async throws { items

    = try await APIClient.share.getItems() } ຖճAPIͷݺͼग़͕͠૸ͬͯ͠·͏ 9
  6. Xcode PreviewΛ࢖͏্Ͱͷ໰୊఺ // ViewModelͷதͷؔ਺ func fetchItems() async throws { items

    = try await APIClient.share.getItems() } ຖճAPIͷݺͼग़͕͠૸ͬͯ͠·͏ UIͷεςʔλεͷίϯτϩʔϧ͕Ͱ͖ͳ͍ APIͷݺͼग़͠ʹ੒ޭͨ࣌͠ͷUI APIͷݺͼग़͠ʹࣦഊͨ࣌͠ͷUI APIΛݺͼग़͍ͯ͠ΔؒͷUI 9
  7. protocol APIClientProtocol { func getItems() async throws -> [Item] }

    class ItemListViewModel: ObservableObject { @Published var items: [Item] = [] let apiClient: APIClientProtocol init(apiClient: APIClientProtocol) { self.apiClient = apiClient } func fetchItems() async throws { items = try await apiClient.getItems() } } 12
  8. protocol APIClientProtocol { func getItems() async throws -> [Item] }

    class ItemListViewModel: ObservableObject { @Published var items: [Item] = [] let apiClient: APIClientProtocol init(apiClient: APIClientProtocol) { self.apiClient = apiClient } func fetchItems() async throws { items = try await apiClient.getItems() } } APIClient༻ʹprotocolΛఆٛ 12
  9. protocol APIClientProtocol { func getItems() async throws -> [Item] }

    class ItemListViewModel: ObservableObject { @Published var items: [Item] = [] let apiClient: APIClientProtocol init(apiClient: APIClientProtocol) { self.apiClient = apiClient } func fetchItems() async throws { items = try await apiClient.getItems() } } APIClient༻ʹprotocolΛఆٛ protocolΛinitͰࠩ͠ࠐΉ 12
  10. protocol APIClientProtocol { func getItems() async throws -> [Item] }

    class ItemListViewModel: ObservableObject { @Published var items: [Item] = [] let apiClient: APIClientProtocol init(apiClient: APIClientProtocol) { self.apiClient = apiClient } func fetchItems() async throws { items = try await apiClient.getItems() } } APIClient༻ʹprotocolΛఆٛ protocolΛinitͰࠩ͠ࠐΉ γϯάϧτϯͷ୅ΘΓʹ DI͞ΕͨAPIClinetΛ࢖͏ 12
  11. struct ItemListView: View { @StateObject let viewModel: ItemListViewModel var body:

    some View { // viewModelͷitemsΛ࢖ͬͯϦετΛදࣔ͢Δ } } 13
  12. struct ItemListView: View { @StateObject let viewModel: ItemListViewModel var body:

    some View { // viewModelͷitemsΛ࢖ͬͯϦετΛදࣔ͢Δ } } ViewModelΛ֎͔Βࠩ͠ࠐΉ 13
  13. private struct MockAPIClient: APIClientProtocol { func getItems() async throws ->

    [Item] { // ϞοΫ͍ͨ͠ॲཧΛ͜͜ʹೖΕΔ } } struct ItemListView_Previews: PreviewProvider { static var previews: some View { let apiClient = MockAPIClient() let viewModel = ItemListViewModel(apiClient: apiClient) ItemListView(viewModel: viewModel) } } 14
  14. όέπϦϨʔͷ՝୊ ݕࡧը໘͸APIΛୟ͚ͩ͘ͳͷʹɺDBͷ৘ใ΋౉͢ඞཁ͕͋Δ ItemListView( viewModel: ItemListViewModel(apiClient: MockAPIClient()), db: MockDatabase(), keychain: MockKeychain(),

    fileManager: MockFileManager(), userDefaults: MockUserDefaults(), authManager: MockAuthManager(), logger: MockLogger(), ... ) ը໘਺͕૿͑Ε͹૿͑Δ΄ͲXcode Previewʹ౉͢ґଘ͕૿͑Δ 17
  15. @Environmentͷ՝୊ 1. ඞཁͳґଘ͕Θ͔Βͳ͍ struct ItemListView_Previews: PreviewProvider { static var previews:

    some View { ItemListView() .environment(\.apiClient, MockAPIClient()) } } 21
  16. @Environmentͷ՝୊ 1. ඞཁͳґଘ͕Θ͔Βͳ͍ struct ItemListView_Previews: PreviewProvider { static var previews:

    some View { ItemListView() .environment(\.apiClient, MockAPIClient()) } } ͜ΕΛ๨Εͯ΋ίϯύΠϧΤϥʔʹͳΒͳ͍ 21
  17. @Environmentͷ՝୊ 2. initͰΞΫηε͕Ͱ͖ͳ͍ struct ItemListView: View { @Environment(\.apiClient) var apiClient:

    APIClientProtocol? @StateObject var viewModel: ItemListViewModel init() { self.viewModel = ItemListViewModel(apiClient: apiClient) } var body: some View { ... } } 22
  18. @Environmentͷ՝୊ 2. initͰΞΫηε͕Ͱ͖ͳ͍ struct ItemListView: View { @Environment(\.apiClient) var apiClient:

    APIClientProtocol? @StateObject var viewModel: ItemListViewModel init() { self.viewModel = ItemListViewModel(apiClient: apiClient) } var body: some View { ... } } initͰEnvironmentͷ஋ʹΞΫηεͰ͖ͣ ViewModel͕࡞Εͳ͍ 22
  19. Xcode Previewͷؔ৺ൣғ Xcode Preview࣌ͷؔ৺͸ର৅ͷը໘͚ͩͰ͋Γɺଞͷը໘ʹؔ৺͸ͳ͍ struct ItemListView: View { ... }

    struct ItemListView_Previews: PreviewProvider { static var previews: some View { ItemListView(viewModel: ...) } } 25
  20. Xcode Previewͷؔ৺ൣғ Xcode Preview࣌ͷؔ৺͸ର৅ͷը໘͚ͩͰ͋Γɺଞͷը໘ʹؔ৺͸ͳ͍ struct ItemListView: View { ... }

    struct ItemListView_Previews: PreviewProvider { static var previews: some View { ItemListView(viewModel: ...) } } ItemListViewʹ͚ͩڵຯ͕͋Γ ભҠઌͷը໘ʹڵຯ͸ͳ͍ 25
  21. DependencyProvider 3. Environment͔ΒґଘΛऔΓग़͠ɺunwrap͢ΔViewΛ࡞Δ struct DependencyProvider<ChildView: View>: View { @Environment(\.dependency) var

    dependency: AppDependency? let childView: (AppDependency) -> ChildView var body: some View { if let dependency { childView(dependency) } else { #if DEBUG Text("Dependency is not set.") // ... .background(.red) #endif 30
  22. DependencyProvider 3. Environment͔ΒґଘΛऔΓग़͠ɺunwrap͢ΔViewΛ࡞Δ struct DependencyProvider<ChildView: View>: View { @Environment(\.dependency) var

    dependency: AppDependency? let childView: (AppDependency) -> ChildView var body: some View { if let dependency { childView(dependency) } else { #if DEBUG Text("Dependency is not set.") // ... .background(.red) #endif Environment͔Β ґଘΛऔΓग़͢ 30
  23. DependencyProvider 3. Environment͔ΒґଘΛऔΓग़͠ɺunwrap͢ΔViewΛ࡞Δ struct DependencyProvider<ChildView: View>: View { @Environment(\.dependency) var

    dependency: AppDependency? let childView: (AppDependency) -> ChildView var body: some View { if let dependency { childView(dependency) } else { #if DEBUG Text("Dependency is not set.") // ... .background(.red) #endif ChildViewΛ֎͔Βࠩ͠ࠐΉ 30
  24. DependencyProvider 3. Environment͔ΒґଘΛऔΓग़͠ɺunwrap͢ΔViewΛ࡞Δ struct DependencyProvider<ChildView: View>: View { @Environment(\.dependency) var

    dependency: AppDependency? let childView: (AppDependency) -> ChildView var body: some View { if let dependency { childView(dependency) } else { #if DEBUG Text("Dependency is not set.") // ... .background(.red) #endif ґଘ͕஫ೖ͞Ε͍ͯΕ͹ ґଘͱڞʹChildViewΛग़͢ 30
  25. DependencyProvider 3. Environment͔ΒґଘΛऔΓग़͠ɺunwrap͢ΔViewΛ࡞Δ struct DependencyProvider<ChildView: View>: View { @Environment(\.dependency) var

    dependency: AppDependency? let childView: (AppDependency) -> ChildView var body: some View { if let dependency { childView(dependency) } else { #if DEBUG Text("Dependency is not set.") // ... .background(.red) #endif } ґଘ͕஫ೖ͞Ε͍ͯͳ͔ͬͨΒ ͦͷ͜ͱΛදࣔ͢Δ 30
  26. DependencyProviderͷ࢖͍ํ 1. ֤ViewͷinitͰ͸ͦͷView͕ඞཁͳґଘ͚ͩΛએݴ͢Δ struct ItemListView: View { init(viewModel: ItemListViewModel) {

    ... } } struct ItemListView_Previews: PreviewProvider { static var previews: some View { let viewModel = ItemListViewModel(apiClient: MockAPIClient()) ItemListView(viewModel: viewModel) } } 31
  27. DependencyProviderͷ࢖͍ํ 2. ଞͷViewΛදࣔ͢Δ࣌ɺDependency ProviderΛڬΜͰґଘΛղܾ͢Δ var body: some View { ...

    .navigationDestination(for: Item.self) { item in DependencyProvider { } } } ·ͣDependencyProviderΛݺͿ 32
  28. DependencyProviderͷ࢖͍ํ 2. ଞͷViewΛදࣔ͢Δ࣌ɺDependency ProviderΛڬΜͰґଘΛղܾ͢Δ var body: some View { ...

    .navigationDestination(for: Item.self) { item in DependencyProvider { dependency in } } } ґଘ͕౉͞ΕΔ 32
  29. DependencyProviderͷ࢖͍ํ 2. ଞͷViewΛදࣔ͢Δ࣌ɺDependency ProviderΛڬΜͰґଘΛղܾ͢Δ var body: some View { ...

    .navigationDestination(for: Item.self) { item in DependencyProvider { dependency in ItemDetailView( item, database: dependency.database ) } } } ౉͞ΕͨґଘΛ࢖ͬͯ Child ViewΛ࡞Δ 32
  30. 33

  31. DependencyProviderͷԿ͕خ͍͔͠ 1. ͲͷView͔ΒͰ΋ґଘ͕औΓग़ͤΔ non-optionalͳ஋ͱͯ͠औΓग़ͤΔ struct FooView: View { var body:

    some View { NavigationLink("࣍ͷը໘΁") { DependencyProvider { dependency in NextView(database: dependency.database) } } } } 34