Created 2025-02-03T06:42:39Z
Updated 2025-02-03T22:46:19Z
Type Research
Status In Progress

https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture

Both MVVM with Coordinator and The Composable Architecture (TCA) are popular architectural patterns for SwiftUI, and each has its own advantages:

  • MVVM with Coordinator:
    • Advantages:
      • Separates concerns by dividing the app into Model, View, and ViewModel layers.
      • Coordinators manage navigation and flow, leading to cleaner view controllers.
      • Generally easier to learn and implement for small to medium-sized projects.
    • Disadvantages:
      • As the project grows, managing state and dependencies can become complex.
      • Requires careful coordination to avoid tight coupling between components.
  • The Composable Architecture (TCA):
    • Advantages:
      • Emphasizes a unidirectional data flow and state management, making it easier to reason about state changes.
      • Promotes composability and modularity, allowing for better reuse of code.
      • Scales well for large projects with complex state management needs.
    • Disadvantages:
      • Steeper learning curve compared to MVVM.
      • Can introduce boilerplate code, making the initial setup more cumbersome.

The choice between MVVM with Coordinator and TCA largely depends on the specific needs of the project, the team’s familiarity with the patterns, and the complexity of the app being developed. For simpler projects, MVVM with Coordinator might be more suitable, while TCA shines in larger projects with more intricate state management requirements.

To implement The Composable Architecture (TCA) principles without relying on third-party dependencies, use the following steps:

  1. State Management:
    • Define a state structure that holds the app’s data.
      struct AppState {
        var counter: Int = 0
      }
      
  2. Actions:
    • Define actions that describe all the possible events in your app.
      enum AppAction {
        case increment
        case decrement
      }
      
  3. Reducer:
    • Create a reducer function that takes the current state and an action, and returns a new state.
      func appReducer(state: inout AppState, action: AppAction) {
        switch action {
        case .increment:
            state.counter += 1
        case .decrement:
            state.counter -= 1
        }
      }
      
  4. Environment:
    • Define any dependencies or environment values the app might need.
      struct AppEnvironment {
        // Add any dependencies here, such as network clients, etc.
      }
      
  5. Store:
    • Create a store to manage state, actions, and the reducer.
      class Store: ObservableObject {
        @Published private(set) var state: AppState
        private let reducer: (inout AppState, AppAction) -> Void
        private let environment: AppEnvironment
             
        init(initialState: AppState, reducer: @escaping (inout AppState, AppAction) -> Void, environment: AppEnvironment) {
            self.state = initialState
            self.reducer = reducer
            self.environment = environment
        }
             
        func send(_ action: AppAction) {
            reducer(&state, action)
        }
      }
      
  6. SwiftUI Integration:
    • Use the store in the SwiftUI views.
      struct ContentView: View {
        @ObservedObject var store: Store
             
        var body: some View {
            VStack {
                Text("Counter: \(store.state.counter)")
                Button("Increment") {
                    store.send(.increment)
                }
                Button("Decrement") {
                    store.send(.decrement)
                }
            }
        }
      }
      
  7. Setup:
    • Create an instance of the store and pass it to the root view.
      @main
      struct MyApp: App {
        var body: some Scene {
            WindowGroup {
                ContentView(store: Store(
                    initialState: AppState(),
                    reducer: appReducer,
                    environment: AppEnvironment()
                ))
            }
        }
      }