Design de software

Padrão Pub-Sub em Swift vs. padrão Observer (guia completo de Event Bus)

Ao criar um app para iOS, cedo ou tarde você encontra um ponto em que fica travado.

4 min de leitura
Imagem de capa de Padrão Pub-Sub em Swift vs. padrão Observer (guia completo de Event Bus)

Ao criar um app para iOS, cedo ou tarde você encontra um ponto em que fica travado.

Quando algo acontece na tela A e uma tela B distante precisa saber, surge a pergunta: como conectar as duas?

Passar delegates de um lado para outro pode deixar o código enrolado como espaguete. É aí que entram o padrão Pub-Sub do Swift e o Event Bus.

Hoje vou esclarecer a diferença entre o padrão Pub-Sub do Swift e o Observer, além de explicar o que é um Event Bus.

Em resumo, no padrão Observer o sujeito conhece diretamente seus assinantes; já o Pub-Sub coloca um intermediário —o Event Bus— para desacoplá-los.

Entendendo essa frase, o restante fica fácil. Vamos analisar tudo passo a passo com código.


Vamos começar pelo padrão Observer

O padrão Observer conecta diretamente o sujeito observado e seus observadores.

Quando o sujeito muda, ele envia notificações diretamente aos observadores registrados.

Se você já trabalhou com iOS, provavelmente usou isso. Exemplos conhecidos são NotificationCenter, o @Published do Combine e KVO (Key-Value Observing).

O ponto principal é que o sujeito mantém diretamente sua própria lista de assinantes.

// O sujeito gerencia diretamente seus assinantes
class Subject {
    private var observers: [Observer] = []
    func add(_ o: Observer) { observers.append(o) }
    func notify() { observers.forEach { $0.update() } }
}

A estrutura é simples e intuitiva, ideal para relações de um para muitos.

Porém, conforme o sistema cresce, exigir que o sujeito conheça seus observadores pode se tornar um problema.


Qual é a diferença do padrão Pub-Sub do Swift?

O padrão Pub-Sub (publicação-assinatura) vai um passo além.

Ele insere um intermediário entre o publicador e o assinante. Esse intermediário é o Event Bus, ou message broker.

O publicador apenas envia ao bus a mensagem “algo aconteceu”. Ele não sabe quem vai receber.

O assinante apenas se registra no bus: “avise-me quando este evento chegar”. Ele não sabe quem enviou.

O essencial é que nenhum dos dois conhece o outro. Isso é chamado de acoplamento fraco, ou desacoplamento.

Conexão direta vs. conexão por um bus, ilustrada
Conexão direta vs. conexão por um bus, ilustrada
// O Event Bus intermedeia a comunicação entre publicadores e assinantes
enum AppEvent { case userLoggedIn(id: String) }

final class EventBus {
    static let shared = EventBus()
    private var handlers: [(AppEvent) -> Void] = []
    func subscribe(_ h: @escaping (AppEvent) -> Void) { handlers.append(h) }
    func publish(_ e: AppEvent) { handlers.forEach { $0(e) } }
}

Para o publicador, basta uma linha: EventBus.shared.publish(.userLoggedIn(id: "123")).

A tela de login nem precisa saber que a tela inicial existe.

Publicar com uma linha de EventBus: é simples assim
Publicar com uma linha de EventBus: é simples assim

Observer vs. Pub-Sub: comparação rápida em uma tabela

Eles parecem semelhantes quando explicados, então organizei as diferenças em uma tabela.

Categoria Padrão Observer Padrão Pub-Sub
Intermediário Não (conexão direta) Sim (Event Bus)
Acoplamento O sujeito conhece os assinantes Não se conhecem (fraco)
Relação Principalmente 1:N N:N é possível
Exemplo no iOS KVO, @Published NotificationCenter, Event Bus
Indicado para Observar o estado de um objeto específico Comunicação entre módulos distantes

O interessante é que NotificationCenter está, na verdade, mais próximo de Pub-Sub.

Embora o nome seja “Notification”, publicador e assinante só se encontram por meio do bus NotificationCenter. Eles não fazem referência direta um ao outro.

Por isso, decorar “padrão Observer = NotificationCenter” é um pouco impreciso. Conceitualmente, ele se aproxima mais de Pub-Sub.


Então, quando usar cada um?

Vou compartilhar o critério que uso no trabalho.

Se você precisa observar de perto a mudança de estado de um objeto, uma abordagem baseada em Observer é conveniente. O @Published do Combine e o @Observable do SwiftUI são perfeitos para isso.

Por outro lado, para eventos importantes entre módulos distantes ou em todo o app —login, pagamento concluído ou perda de rede—, um Event Bus é muito mais limpo.

Ainda assim, o Event Bus não é uma solução universal.

Se usado em excesso, fica difícil rastrear o fluxo: “de onde esse evento está vindo?”. Você ganha desacoplamento, mas perde um pouco de visibilidade.

Por isso, uso Observer (Combine) na lógica interna das telas e deixo o Event Bus apenas para eventos importantes que atravessam telas e módulos.

Concentrar apenas os eventos importantes no bus deixa o fluxo mais limpo
Concentrar apenas os eventos importantes no bus deixa o fluxo mais limpo

P. Se eu uso Combine, ainda preciso de um Event Bus?

Não. Com apenas um PassthroughSubject do Combine, você pode criar um Event Bus simples. As ferramentas se sobrepõem, mas os conceitos não são substituídos.

P. Pub-Sub é sempre o melhor padrão?

Não. Se a relação for próxima e não houver necessidade de desacoplamento, Observer é mais fácil de ler e depurar.


Observer é uma conexão direta; Pub-Sub é uma conexão com um intermediário. Essa é toda a diferença.

Eles não competem: são ferramentas escolhidas conforme a situação. Ao criar sua próxima tela, pense primeiro em quanto os dois lados precisam conhecer um ao outro; o padrão adequado logo ficará claro.

Continue lendo