Introduction
The Composable Architecture (TCA
, for short) is a library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind. It can be used in SwiftUI, UIKit, and more, and on any Apple platform (iOS, macOS, tvOS, and watchOS).
This library provides a few core tools that can be used to build applications of varying purpose and complexity. It provides compelling stories that you can follow to solve many problems you encounter day-to-day when building applications, such as:
-
State management: How to manage the state of your application using simple value types, and share state across many screens so that mutations in one screen can be immediately observed in another screen.
-
Composition: How to break down large features into smaller components that can be extracted to their own, isolated modules and be easily glued back together to form the feature.
-
Side effects: How to let certain parts of the application talk to the outside world in the most testable and understandable way possible.
-
Testing: How to not only test a feature built in the architecture, but also write integration tests for features that have been composed of many parts, and write end-to-end tests to understand how side effects influence your application. This allows you to make strong guarantees that your business logic is running in the way you expect.
-
Ergonomics: How to accomplish all of the above in a simple API with as few concepts and moving parts as possible.
Basic Usage
To build a feature using the Composable Architecture you define some types and values that model your domain:
-
State: A type that describes the data your feature needs to perform its logic and render its UI.
-
Action: A type that represents all of the actions that can happen in your feature, such as user actions, notifications, event sources and more.
-
Reducer: A function that describes how to evolve the current state of the app to the next state given an action. The reducer is also responsible for returning any effects that should be run, such as API requests, which can be done by returning an Effect value.
-
Store: The runtime that actually drives your feature. You send all user actions to the store so that the store can run the reducer and effects, and you can observe state changes in the store so that you can update UI.
The First TCA App
Let’s build a simple counter app using TCA. The app will have two buttons, one to increment the counter and one to decrement the counter. The app will also have a reset button to reset the counter to zero.
Preqrequisites
- The Composable Architecture
1.7.0
. - iOS 17.0+
Create a new project
- Open Xcode and create a new project.
- Add The Composable Architecture package to the project. Go to
File > Swift Packages > Add Package Dependency...
and enter the following URL:https://github.com/pointfreeco/swift-composable-architecture
.
Create the UI
-
Let’s create the UI for the app like below:
-
Open
ContentView.swift
and replace the code with the following code:
|
|
Start building with TCA
- First we need access to the Composable Architecture, so letβs import it:
|
|
- And now we can start building our feature. We create a new type that will house the domain and behavior of the feature, and it will be annotated with the
@Reducer
macro:
|
|
- We can even just look at the view to see exactly what is needed: a integer for the current count, an optional string for the fact if it is being shown, and a boolean that determines whether or not a timer is currently on:
|
|
- Next we need to define the actions that can happen in this view. This is almost always an enum:
|
|
- And then we implement the body property, which is responsible for composing the actual logic and behavior for the feature. In it we can use the Reduce reducer to describe how to change the current state to the next state, and what effects need to be executed. Some actions donβt need to execute effects, and they can
return .none
to represent that:
|
|
- And then finally we define the view that displays the feature. It holds onto a
StoreOf<CounterReducer>
so that it can observe all changes to the state and re-render, and we can send all user actions to the store so that state changes:
|
|
Testing your TCA app
- Let’s write some tests for the app.
- Create your first test and add the following code:
|
|
- Run the test and you will see the following error:
|
|
- To fix this we need to mutate $0 to make its count 1:
|
|
- Now the test passes.
Testing with dependencies
- In tests we can use a mock dependency that immediately returns a deterministic, predictable fact. We can start by wrapping the number fact functionality in a new type:
|
|
- And then registering that type with the dependency management system by conforming the client to the DependencyKey protocol, which requires you to specify the live value to use when running the application in simulators or devices:
|
|
- And then we can update the reducer to use this dependency:
|
|
- And the test store can be constructed without specifying any dependencies, but you can still override any dependency you need to for the purpose of the test:
|
|