En los debates sobre arquitectura de la era de SwiftUI hay un nombre imprescindible: TCA (The Composable Architecture), creado por Point-Free.
Si MVVM (Model-View-ViewModel) y Clean Architecture, tratados en los artículos anteriores, hablaban de «cómo dividir las responsabilidades», TCA plantea una pregunta distinta: «¿Podemos controlar con una sola regla todas las rutas por las que cambia el estado?»
Hoy resumiremos cómo funciona el flujo de datos unidireccional de TCA y qué obtenemos y qué pagamos a cambio.
Tres ingredientes: State, Action, Reducer
En TCA, una pantalla —o una funcionalidad— se define mediante tres elementos.
- State: Una única estructura que contiene todo el estado de esta funcionalidad
- Action: Un enum con todos los eventos que pueden ocurrir en esta funcionalidad: toques de botones, respuestas recibidas, notificaciones recibidas y todo lo demás
- Reducer: Una función pura que define cómo cambia el State cuando llega este Action en el State actual
@Reducer
struct Counter {
@ObservableState
struct State: Equatable {
var count = 0
}
enum Action {
case incrementTapped
case decrementTapped
}
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .incrementTapped:
state.count += 1
return .none
case .decrementTapped:
state.count -= 1
return .none
}
}
}
}
La View no puede cambiar el estado directamente. Solo puede enviar un Action al Store. Cuando el Store ejecuta el Reducer y cambia el State, la View renderiza el State actualizado.
사건 발생 → Action 전송 → Reducer가 State 변경 → View 갱신
Ese ciclo unidireccional lo abarca todo. No hay rutas ocultas para cambiar el estado. El laberinto de depuración de «¿dónde cambió exactamente este valor?» desaparece por diseño.
Los efectos secundarios también siguen las reglas
Las tareas asíncronas, como las solicitudes de red, no las ejecuta directamente el Reducer, sino que las devuelve como Effect. Cuando termina el Effect, el resultado vuelve convertido en un Action.
case .refreshTapped:
state.isLoading = true
return .run { send in
let posts = try await postClient.fetch()
await send(.postsLoaded(posts))
}
case .postsLoaded(let posts):
state.isLoading = false
state.posts = posts
return .none
Las dependencias, como postClient, se incorporan mediante el sistema de inyección de dependencias de TCA, así que en las pruebas se sustituyen por un cliente simulado. Aquí aparece el arma más potente de TCA: TestStore.
let store = TestStore(initialState: Feed.State()) { Feed() }
await store.send(.refreshTapped) { $0.isLoading = true }
await store.receive(.postsLoaded(mockPosts)) {
$0.isLoading = false
$0.posts = mockPosts
}
Obliga a especificar por completo cómo debe cambiar el State para cada Action. Si se produce aunque sea un cambio de estado inesperado, la prueba falla. A las pruebas de MVVM les resulta difícil igualar este nivel de verificación de integridad.
¿Cuál es el coste?
Esta precisión tiene un precio.
**La curva de aprendizaje es pronunciada.**Antes de ser productivo hay que aprender muchos conceptos, como Reducer, Effect, Store, Scope y el sistema de dependencias. Para los equipos sin experiencia en programación funcional, aún más.
**Hay código repetitivo.**Incluso para cambiar un solo valor hay que crear un caso de Action y añadir una rama al Reducer. En una pantalla sencilla, este ritual resulta excesivo.
**Es una dependencia de terceros.**TCA es una biblioteca creada por Point-Free, no por Apple. Para adaptarse a los cambios anuales de SwiftUI, TCA también ha pasado por varias migraciones importantes, y todas las pantallas de la app terminan construidas sobre esta biblioteca. Es un proyecto maduro y mantenido activamente, pero decidir confiar el esqueleto de la app a una dependencia externa no es una decisión menor.
¿Para qué equipos es adecuado?
Las condiciones en las que TCA aporta valor son bastante claras.
- Aplicaciones con estados muy entrelazados, como documentos colaborativos, finanzas, formularios complejos y sincronización en tiempo real
- Dominios donde las pruebas completas de los cambios de estado son importantes
- Equipos familiarizados con conceptos funcionales o capaces de invertir en aprendizaje
Por el contrario, si la mayoría de las pantallas de una app solo «reciben y muestran» datos, MV (Model-View)/MVVM más un UseCase, como vimos en la parte 3, suele ser suficiente. TCA no es una opción equivocada, sino una opción costosa, y brilla cuando la complejidad del estado justifica ese coste.
Resumen
- TCA define las funcionalidades con State, Action y Reducer, y obliga a que las rutas de cambio de estado sigan un único ciclo unidireccional.
- Los efectos secundarios también entran en las reglas mediante Effect → Action, y TestStore verifica la integridad de los cambios de estado.
- El precio es la curva de aprendizaje, el código repetitivo y la decisión de confiar el esqueleto de la app a un tercero.
- Es una arquitectura con una compensación clara: cuanto mayor es la complejidad del estado, mayor es el beneficio.
Solo queda la última pregunta de la serie. Entre tantas opciones, desde MVC hasta TCA, **¿qué debería elegir nuestro equipo?**En el próximo artículo cerraremos la serie organizando los criterios de elección.

![Imagen de portada de [Arquitectura de iOS #7] TCA y flujo de datos unidireccional](/assets/images/posts/caac9fa2-c44c-4646-87b0-01421048db10/1.jpg)