Engenharia iOS

[Arquitetura do iOS #7] TCA e fluxo de dados unidirecional

Há um nome indispensável nas discussões de arquitetura da era SwiftUI: TCA (The Composable Architecture), criado pela Point-Free.

4 min de leitura
Imagem de capa de [Arquitetura do iOS #7] TCA e fluxo de dados unidirecional

Há um nome indispensável nas discussões de arquitetura da era SwiftUI: TCA (The Composable Architecture), criado pela Point-Free.

Se o MVVM (Model-View-ViewModel) e a Clean Architecture, abordados nos artigos anteriores, discutiam “como dividir as responsabilidades”, o TCA parte de uma pergunta diferente: “Será que podemos controlar todos os caminhos de mudança de estado com uma única regra?”

Hoje, vamos resumir como funciona o fluxo de dados unidirecional do TCA e o que ganhamos e pagamos por isso.


Três ingredientes: State, Action, Reducer

No TCA, uma tela — ou funcionalidade — é definida por três elementos.

  • State: Uma única estrutura que contém todo o estado dessa funcionalidade
  • Action: Um enum com todos os eventos que podem ocorrer nessa funcionalidade: toque em botão, chegada de resposta, recebimento de notificação e tudo mais
  • Reducer: Uma função pura que define como o State muda quando esse Action chega ao State atual
@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
            }
        }
    }
}

A View não pode alterar o estado diretamente. Tudo o que ela pode fazer é enviar um Action para o Store. Quando o Store executa o Reducer e altera o State, a View renderiza o State atualizado.

사건 발생 → Action 전송 → Reducer가 State 변경 → View 갱신

Esse ciclo de mão única é tudo. Não há atalhos para alterar o estado. O labirinto de depuração de “onde foi que esse valor mudou?” desaparece estruturalmente.


Os efeitos colaterais também entram nas regras

Operações assíncronas, como requisições de rede, não são executadas diretamente pelo Reducer; ele as retorna como um Effect. Quando o Effect termina, o resultado volta como um 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

Dependências como postClient são injetadas pelo sistema de injeção de dependência do TCA, então os testes podem substituí-las por um cliente falso. É aqui que surge a arma mais poderosa do 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
}

Para cada Action, ele força você a especificar exatamente como o State deve mudar. Se ocorrer uma única mudança de estado inesperada, o teste falha. É difícil reproduzir esse nível de verificação de completude em testes de MVVM.

O TestStore força a especificação de cada mudança de estado
O TestStore força a especificação de cada mudança de estado

Qual é o custo?

Essa precisão vem com uma conta.

**A curva de aprendizado é íngreme.**Antes de ganhar produtividade, é preciso aprender muitos conceitos, como Reducer, Effect, Store, Scope e o sistema de dependências. Para equipes sem experiência em programação funcional, isso é ainda mais difícil.

**Há código repetitivo.**Até para alterar um único valor é preciso criar um caso de Action e adicionar uma ramificação ao Reducer. Em telas simples, esse ritual é exagerado.

**É uma dependência de terceiros.**O TCA é uma biblioteca criada pela Point-Free, não pela Apple. Para acompanhar as mudanças anuais do SwiftUI, o TCA também passou por várias migrações grandes, e todas as telas do app acabam construídas sobre essa biblioteca. É um projeto maduro e mantido ativamente, mas decidir confiar a estrutura do app a uma dependência externa não é algo trivial.


Para que tipo de equipe ele serve?

As condições em que o TCA vale a pena são relativamente claras.

  • Apps com estados muito interligados, como documentos colaborativos, finanças, formulários complexos e sincronização em tempo real
  • Domínios em que testar completamente as mudanças de estado é importante
  • Equipes familiarizadas com conceitos funcionais ou capazes de investir em aprendizado

Por outro lado, se a maioria das telas do app apenas “recebe e exibe” dados, MV (Model-View)/MVVM com um UseCase, como vimos na parte 3, costuma ser suficiente. O TCA não é uma escolha errada, mas uma escolha cara, e brilha quando a complexidade do estado justifica esse custo.

O TCA não é uma escolha errada, mas uma escolha cara
O TCA não é uma escolha errada, mas uma escolha cara

Resumo

  • O TCA define funcionalidades com State, Action e Reducer e força todos os caminhos de mudança de estado a seguirem um único ciclo unidirecional.
  • Os efeitos colaterais também entram nas regras como Effect → Action, e o TestStore verifica a completude das mudanças de estado.
  • O custo é a curva de aprendizado, o código repetitivo e a decisão de confiar a estrutura do app a terceiros.
  • É uma arquitetura com um trade-off claro: quanto maior a complexidade do estado do app, maior o benefício.

Resta apenas a última pergunta da série. Entre tantas opções, do MVC ao TCA, **o que a nossa equipe deve escolher?**No próximo artigo, vamos encerrar a série organizando os critérios de escolha.

Continue lendo