Você já precisou abrir if e alterar dez ifs só para mudar um estado de tela?
Quando os estados de carregamento, sucesso e falha são todos tratados com if isLoading e else if hasError, o código rapidamente fica difícil de manter.
Hoje vou explicar, com base em experiências reais, como organizar o inferno de ifs em objetos de estado usando o State Pattern do Swift.
Indo direto ao ponto: o State Pattern significa separar cada estado em um objeto próprio (ou enum) e colocar a lógica de transição dentro desse objeto. Assim, as ramificações condicionais ficam concentradas em um único lugar.
A diferença em relação ao Strategy Pattern, cuja estrutura é quase igual, está em quem conduz a mudança. Strategy Pattern vs. State Pattern permite distinguir primeiro a seleção externa da transição interna de estado.
O que você vai aprender
- Por que o inferno de ifs acontece
- Duas formas de dividir estados em objetos no Swift
- Quando usar enum ou protocol
- Uma sequência de refatoração pronta para o trabalho
Por que o inferno de ifs acontece?
No início, com apenas dois estados, if parece suficiente. Mas, conforme os requisitos crescem, eles viram 5 ou 6: carregando, sucesso, falha, tela vazia, tentando novamente e assim por diante.
O problema é que essas combinações de estado ficam espalhadas por vários métodos. Elas se repetem na lógica de ativação de botões como if, na atualização da tela como if e nos callbacks de rede como if. Por isso, ao adicionar um estado, você precisa encontrar e corrigir cada bloco if; esquecer um já é suficiente para criar um bug.
Quando os estados ficam espalhados, os bugs se escondem; quando são reunidos, eles aparecem.
O State Pattern existe justamente para impedir essa “dispersão”.
Como dividir estados em objetos no Swift (abordagem com enum)
A forma mais leve de começar é enum. Você define os estados como valores e pode armazenar os dados necessários com valores associados. Este é um exemplo de estados de tela representados por um enum.
enum ViewState {
case loading
case loaded(items: [String]) // Anexar dados em caso de sucesso
case failed(message: String) // Anexar o motivo da falha
case empty
}
Agora a atualização da tela termina com switch uma única vez. Graças aos valores associados, não existe mais um estado ambíguo como “sucesso com dados nil”.
switch state {
case .loading: showSpinner()
case .loaded(let items): render(items)
case .failed(let msg): showError(msg)
case .empty: showEmptyView()
}
Para a maioria dos estados de tela, a abordagem com enum é suficiente. switch detecta casos esquecidos em tempo de compilação, deixando a adição de estados mais segura.
enum vs. protocol: quando usar cada um?
Se o comportamento de cada estado for muito diferente e as regras de transição forem complexas, a abordagem com protocol é melhor. Cada estado vira um tipo que retorna o próximo estado por conta própria. As diferenças são estas.
| Categoria | Abordagem com enum | Abordagem com protocol |
|---|---|---|
| Situação adequada | Estados centrados em dados | Cada estado tem um comportamento diferente |
| Adicionar estado | Adicionar case | Adicionar tipo |
| Lógica de transição | switch externo | Encapsulada dentro dos objetos de estado |
| Curva de aprendizado | Baixa | Um pouco mais alta |
O ponto central da abordagem com protocol é que cada objeto assume a responsabilidade pelas transições de estado.
protocol PlayerState {
func play() -> PlayerState // Retornar o próximo estado
func pause() -> PlayerState
}
struct PlayingState: PlayerState {
func play() -> PlayerState { self } // Manter se já estiver reproduzindo
func pause() -> PlayerState { PausedState() } // Mudar para pausado
}
Assim, regras como “o que acontece se eu pressionar pause durante a reprodução?” existem apenas dentro daquele estado. As ramificações desaparecem, e cada estado precisa conhecer apenas as próprias regras.
Refatoração prática: siga esta ordem
Tentar mudar tudo de uma vez pode assustar. Eu fiz a migração aos poucos, seguindo esta ordem.
- Anotar a lista de estados realmente representados pelo
ifespalhado - Definir esses estados em um único enum
- Trocar primeiro o código de atualização da tela por
switch - Eliminar de vez com enum as combinações impossíveis, como carregamento e erro ao mesmo tempo
- Promover para protocol apenas as partes com regras de transição complexas
O ponto é começar com um enum e promovê-lo para protocol somente quando necessário. Projetar tudo com protocol desde o início pode deixar o código mais pesado.
Perguntas frequentes (Q&A)
P. Preciso usar o padrão mesmo com apenas 3 estados?
Não necessariamente. Porém, se os estados estiverem espalhados por vários métodos, vale reuni-los em um enum independentemente da quantidade.
P. Posso usar isso também com SwiftUI?
Sim. Funciona muito bem manter o estado no formato @Published var state: ViewState e fazer a ramificação na view com switch.
P. Um enum não fica bagunçado quando tem muitos valores associados?
Quando passam de 3 valores associados, recomendo agrupá-los em uma struct separada e armazená-los como case loaded(Result).
Pode parecer estranho no começo, mas, depois que você aprende a reunir estados em objetos, não vai querer voltar ao código antigo. Comece organizando uma tela pequena com um enum. Você vai sair do inferno de ifs mais rápido do que imagina. Boa sorte!

