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.
// 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.
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.
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.

