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

SwiftUIに最適のアーキテクチャ

 SwiftUIに最適のアーキテクチャ

Mike Apurin

March 29, 2024
Tweet

More Decks by Mike Apurin

Other Decks in Programming

Transcript

  1. ཁ݅ SwiftUI First – Xcode Previews͕ਏ͘ͳ͍ – Ϛϧν΢Πϯυ΢ + Ϛϧνεςʔτ͕Մೳ

    – UIKit, App Intents΋ߟ͑ΒΕ͍ͯΔ – ֎෦ϥΠϒϥϦͷґଘ͕ೱ͘ͳ͍ –
  2. Naive MV struct ContentView: View { let api: APIClient @State

    var products: [Product] = [] var body: some View { ForEach(products) { product in Button("Buy \(product.name)") { Task { await api.execute( BuyProductsRequest(product) ) } } } .task { products = await api.execute( GetProductsRequest() ) } } }
  3. DI஫ೖ struct AppActions { struct Products { var buy: (Product)

    async -> Void var refresh: () async -> Void } } ActionsΛ੾Γ෼͚Δ –
  4. DI஫ೖ struct AppState { final class Products: ObservableObject { @Published

    var products: [Product] = [] } } StateΛ੾Γ෼͚Δ –
  5. struct ContentView: View { @ObservedObject var state: AppState.Products let actions:

    AppActions.Products var body: some View { ForEach(state.products) { product in Button("Buy \(product.name)") { Task { await actions.buy(product) } } } .task { await actions.refresh() } } }
  6. StateͱActionsΛଋͶͯ Dependency ͱͯ͠ఆٛ struct AppState { final class Products: ObservableObject

    { ... } var products = Products() } struct AppActions { struct Products { ... } var products: Products } extension EnvrionmentValues { var appActions: AppActions { ... } } struct AppDependency { var state: AppState var actions: AppActions }
  7. Dependency Λอ࣋͢ΔίϯςφΛ ఆٛ protocol AppContainer { var app: AppDependency {

    get set } } MockAppContainer ͱ LiveAppContainer Λ ४උ
  8. struct ContentView: View { @EnvironmentObject var state: AppState.Products @Environment(\.appActions.products) let

    actions var body: some View { ForEach(state.products) { product in Button("Buy \(product.name)") { Task { await actions.buy(product) } } } .task { await actions.refresh() } } }
  9. ஫ೖΛ͠΍͘͢͢Δ public protocol ViewInjectable { typealias Content = ViewDependencyModifier<Self>.Content associatedtype

    InjectedBody: View func inject(content: Content) -> InjectedBody } public struct ViewDependencyModifier<D: ViewInjectable>: ViewModifier { let dependency: D public func body(content: Content) -> some View { dependency.inject(content: content) } } extension View { public func dependency(_ dependency: some ViewInjectable) -> some View { modifier(ViewDependencyModifier(dependency: dependency)) } }
  10. Service Λ࣮૷ final class ProductsService { let api: APIClient let

    state: AppState.Products func buy(product: Product) async { await api.execute( BuyProductsRequest(product) ) } func refresh() async { products = await api.execute( GetProductsRequest() ) } }
  11. LiveAppContainer Ͱ઀ଓ final class LiveAppContainer: AppContainer { struct Configuration {

    ... } var app: AppDependency private let api: APIClient private let productsService: ProductsService init(configuration: Configuration) { self.app = .init() self.api = ... self.productsService = .init( api: api, state: app.state.products ) app.actions.products.buy = productsService.buy(product:) app.actions.products.refresh = productsService.refresh } }
  12. Previews #Preview { ContentView() .mockContainer(.app) } #Preview { WithMockContainer(.app) {

    container in MainFlow(container: container) } } ( .mockContainer ͱ WithMockContainer ͷ࣮૷͸ׂѪ)
  13. View – ͨͩͷUI෦඼ – Screen – ը໘શମΛදࣔ͢ΔUI෦඼ – Screen ಉ࢜Λ࡞੒͠ͳ͍

    – Flow – DependencyContainer Λอ࣋ͯ͠஫ೖ͢Δ – Screen Λ࡞੒͢Δ – ભҠ –
  14. AppͰίϯςφΛੜ੒ @main struct SampleApp: App { @State var container =

    LiveAppContainer( configuration: ... ) var body: some Scene { WindowGroup { MainFlow(container: container) .dependency(container) } } }