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

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

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

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

Avatar for Mike Apurin

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) } } }