Diseño de software

Patrón Pub-Sub en Swift frente al patrón Observer (guía del bus de eventos)

Al crear una app para iOS, tarde o temprano aparece un punto en el que uno se atasca.

4 min de lectura
Imagen de portada de Patrón Pub-Sub en Swift frente al patrón Observer (guía del bus de eventos)

Al crear una app para iOS, tarde o temprano aparece un punto en el que uno se atasca.

Cuando algo ocurre en la pantalla A y una pantalla B lejana necesita saberlo, llega la pregunta: ¿cómo las conectamos?

Pasar delegates de un lado a otro puede enredar el código como espaguetis. Ahí entran en juego el patrón Pub-Sub de Swift y el bus de eventos.

Hoy aclararé en qué se diferencia Pub-Sub en Swift del patrón Observer y qué es exactamente un bus de eventos.

En resumen, el patrón Observer hace que el sujeto conozca directamente a sus suscriptores, mientras que Pub-Sub coloca un intermediario —el bus de eventos— para desacoplarlos.

Si entiendes esta frase, lo demás resulta mucho más sencillo. Vamos a verlo paso a paso con código.


Empecemos por el patrón Observer

El patrón Observer conecta directamente el sujeto observado y sus observadores.

Cuando el sujeto cambia, envía notificaciones directamente a los observadores registrados.

Si has trabajado con iOS, probablemente ya lo hayas usado. Algunos ejemplos son NotificationCenter, @Published de Combine y KVO (Key-Value Observing).

La clave es que el sujeto mantiene directamente su propia lista de suscriptores.

// El sujeto administra directamente a sus suscriptores
class Subject {
    private var observers: [Observer] = []
    func add(_ o: Observer) { observers.append(o) }
    func notify() { observers.forEach { $0.update() } }
}

La estructura es simple e intuitiva, y encaja bien con relaciones uno a muchos.

Sin embargo, a medida que el sistema crece, puede resultar costoso que el sujeto tenga que conocer a sus observadores.


¿En qué se diferencia el patrón Pub-Sub de Swift?

El patrón Pub-Sub (publicación-suscripción) va un paso más allá.

Inserta un intermediario entre el publicador y el suscriptor. Ese intermediario es el bus de eventos, o message broker.

El publicador solo envía al bus el mensaje «ha ocurrido esto». No sabe quién lo recibe.

El suscriptor solo se registra en el bus: «avísame cuando llegue este evento». No sabe quién lo envió.

La clave es que ninguno conoce al otro. Esto se denomina acoplamiento débil o desacoplamiento.

Conexión directa frente a conexión mediante un bus, ilustrada
Conexión directa frente a conexión mediante un bus, ilustrada
// El bus de eventos intermedia entre publicadores y suscriptores
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 el publicador, basta con una línea: EventBus.shared.publish(.userLoggedIn(id: "123")).

La pantalla de inicio de sesión ni siquiera necesita saber que existe la pantalla principal.

Publicar con una línea de EventBus: así de sencillo
Publicar con una línea de EventBus: así de sencillo

Observer vs. Pub-Sub: comparación rápida en una tabla

Al oírlos, parecen similares, así que resumí las diferencias en una tabla.

Categoría Patrón Observer Patrón Pub-Sub
Intermediario No (conexión directa) Sí (bus de eventos)
Acoplamiento El sujeto conoce a los suscriptores No se conocen (débil)
Relación Principalmente 1:N Es posible N:N
Ejemplo en iOS KVO, @Published NotificationCenter, bus de eventos
Situación adecuada Observar el estado de un objeto concreto Comunicación entre módulos alejados

Lo interesante es que NotificationCenter se acerca más a Pub-Sub.

Aunque se llama «Notification», publicadores y suscriptores solo se encuentran a través del bus NotificationCenter. No se referencian directamente.

Por eso, memorizar «patrón Observer = NotificationCenter» resulta ligeramente impreciso. Conceptualmente, está más cerca de Pub-Sub.


Entonces, ¿cuándo conviene usar cada uno?

Este es el criterio que sigo en la práctica.

Si necesitas observar de cerca los cambios de estado de un objeto, una solución basada en Observer resulta cómoda. @Published de Combine y @Observable de SwiftUI encajan perfectamente.

En cambio, para comunicar eventos importantes entre módulos alejados o en toda la app —inicio de sesión, pago completado o pérdida de red—, un bus de eventos resulta mucho más limpio.

Eso sí, el bus de eventos tampoco es una solución universal.

Si se usa en exceso, es difícil seguir el flujo: «¿de dónde sale exactamente este evento?». Ganas desacoplamiento, pero pierdes algo de visibilidad.

Por eso uso Observer (Combine) para la lógica interna de cada pantalla y el bus de eventos solo para eventos importantes que cruzan pantallas y módulos.

Si concentras en el bus solo los eventos importantes, el flujo queda más limpio
Si concentras en el bus solo los eventos importantes, el flujo queda más limpio

P. Si uso Combine, ¿ya no necesito un bus de eventos?

No. Con un solo PassthroughSubject de Combine puedes crear tu propio bus de eventos sencillo. Las herramientas se solapan, pero los conceptos no se sustituyen.

P. ¿Pub-Sub es siempre el mejor patrón?

No necesariamente. Si la relación es cercana y no hace falta desacoplarla, Observer es más fácil de leer y depurar.


Observer es una conexión directa; Pub-Sub, una conexión mediante un intermediario. Esa es toda la diferencia.

No compiten entre sí: son herramientas que se eligen según la situación. Al diseñar tu próxima pantalla, piensa primero cuánto deben conocerse ambas partes; enseguida sabrás qué patrón encaja.

Seguir leyendo