TCA, or The Composable Architecture, is a framework for iOS development that provides a structured and scalable approach to building robust, maintainable applications. Created by Brandon Williams and Stephen Celis, TCA leverages functional programming principles and Swift's powerful type system to offer a modern solution for iOS app architecture.
In this post, we’ll explore how to migrate our Rick and Morty iOS app to TC
The architecture
TCA consists of five main components:
- State: A single type that represents the entire state of an app or feature.
- Actions: An enumeration of all possible events that can occur in the app.
- Environment: A type that wraps all dependencies of the app or feature.
- Reducer: A function that transforms the current state to the next state based on a given action.
- Store: The runtime that powers the feature and manages the state.
TCA offers several advantages for iOS development:
- Unidirectional data flow: This makes it easy to understand how changes in state occur, simplifying debugging and preventing unexpected side effects.
- Improved testability: TCA encourages writing features that are testable by default.
- Modularity: It allows for composing separate features, enabling developers to plan, build, and test each part of the app independently.
- Scalability: TCA is particularly useful for complex applications with many states and interactions.
Configure XCode project
To get started with this architecture, integrate the ComposableArchitecture library from its GitHub repository.

Downloading might take some time.

Character feature
The component that we have to change implentation is basically the ViewModel component. In this case will be renamed as CharacterFeature.
import ComposableArchitecture
@Reducer
struct CharatersFeature {
@ObservableState
struct State: Equatable {
var characters: [Character] = []
var isLoading: Bool = false
}
enum Action {
case fetchCharacters
case fetchCharactersSuccess([Character])
}
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .fetchCharacters:
state.isLoading = true
state.characters = []
return .run { send in
let result = await currentApp.dataManager.fetchCharacters(CharacterService())
switch result {
case .success(let characters):
//state.characters = characters
await send(.fetchCharactersSuccess(characters))
case .failure(let error):
print(error)
}
}
case .fetchCharactersSuccess(let characters):
state.isLoading = false
state.characters = characters
return .none
}
}
}
}
This code defines a feature using the Composable Architecture (TCA) framework in Swift. Let’s break down what this code does:
- Import and Structure:
- It imports the ComposableArchitecture framework.
- It defines a
CharatersFeature
struct with the@Reducer
attribute, indicating it’s a reducer in the TCA pattern.
- State:
- The
State
struct is marked with@ObservableState
, making it observable for SwiftUI views. - It contains two properties:
characters
: An array ofCharacter
objects.isLoading
: A boolean to track if data is being loaded.
- The
- Actions:
- The
Action
enum defines two possible actions:fetchCharacters
: Triggers the character fetching process.fetchCharactersSuccess
: Handles successful character fetching.
- The
- Reducer:
- The
body
property defines the reducer logic. - It uses a
Reduce
closure to handle state changes based on actions.
- The
- Action Handling:
- For
.fetchCharacters
:- Sets
isLoading
to true and clears the characters array. - Runs an asynchronous operation to fetch characters.
- On success, it dispatches a
.fetchCharactersSuccess
action. - On failure, it prints the error.
- Sets
- For
.fetchCharactersSuccess
:- Sets
isLoading
to false. - Updates the
characters
array with the fetched data.
- Sets
- For
- Asynchronous Operations:
- It uses
.run
for handling asynchronous operations within the reducer. - The character fetching is done using
currentApp.dataManager.fetchCharacters(CharacterService())
.
- It uses
This code essentially sets up a state management system for fetching and storing character data, with loading state handling. It’s designed to work with SwiftUI and the Composable Architecture, providing a structured way to manage application state and side effects.
View
The view is almost the same as before:
struct CharacterView: View {
let store: StoreOf<CharatersFeature>
var body: some View {
NavigationView {
ZStack {
if store.isLoading {
ProgressView()
}
ScrollView {
ForEach(store.characters) { character in
NavigationLink {
DetailView(character: character)
} label: {
HStack {
characterImageView(character.imageUrl)
Text("\(character.name)")
Spacer()
}
}
}
}
}
}
.padding()
.onAppear {
store.send(.fetchCharacters)
}
}
The store holds observable items used by the view to present either the progression view or the character list. When the view appears, it triggers the .fetchCharacters
action, prompting the reducer to fetch the character list.
Unit test
Unit testing with TCA differs significantly from my expectations:
@Test func example() async throws {
// Write your test here and use APIs like `#expect(...)` to check expected conditions.
let store = await TestStore(initialState: CharatersFeature.State()) {
CharatersFeature()
}
await store.send(.fetchCharacters) {
$0.isLoading = true
}
await store.receive(\.fetchCharactersSuccess, timeout: .seconds(1)) {
$0.isLoading = false
$0.characters = expCharacters
}
await store.finish()
}
In TCA, testing often focuses on asserting the state transitions and effects of the reducer. Instead of traditional XCTest assertions like XCTAssertEqual
, TCA provides its own mechanism for testing reducers using TestStore
, which is a utility designed to test state changes, actions, and effects in a deterministic way.
Conclusions
This is a very minimalistic example just to get in touch with this architecture. With more complex applications, I meain with some flows and many screens reducer would become a huge chunk of code, so god approach would be implement this pattern per app flow.You can find source code used for writing this post in following repository.
References
- swift-composable-architecture
GitHub repository
- Meet the Composable Architecture
Tutorial