Combine #13: Manejo de Recursos
Source: Dev.to
share()
La mayoría de los Publishers de Combine son struct que solo describen un pipeline, sin guardar un estado compartido. No se crea una instancia viva que mantenga datos, sino un valor que describe cómo se producirá el flujo cuando alguien se suscriba. Por esta razón, cada suscripción inicia una nueva secuencia de publicación (es decir, arranca su propio trabajo nuevo).
share() devuelve un Publisher.Share, que se implementa sobre multicast(_:) y PassthroughSubject, creando una única suscripción al Publisher original que se comparte entre múltiples suscriptores. Como Combine usa una clase internamente para mantener referencias a los suscriptores, se dice que hay un estado compartido.
let shared = URLSession.shared
.dataTaskPublisher(for: URL(string: "https://ejemplo.com")!)
.map(\.data)
.share()
let subscription1 = shared.sink { /* … */ }
let subscription2 = shared.sink { /* … */ }
// Sólo se hace una vez la petición web y el resultado se comparte
// por ambas suscripciones
Cuando share() recibe la primera suscripción, inicia el trabajo del Publisher de entrada. Esto puede implicar que el flujo termine (emitiendo un evento de fin) antes de que ocurran otras suscripciones; en ese caso, las nuevas suscripciones recibirán solo el evento de fin.
let shared = URLSession.shared
.dataTaskPublisher(for: URL(string: "https://ejemplo.com")!)
.map(\.data)
.share()
let subscription1 = shared.sink { /* … */ }
var subscription2: AnyCancellable? = nil
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
subscription2 = shared.sink { /* … */ } // solo recibe el evento de fin
}
multicast(_:)
multicast(_:) usa un Subject para emitir valores a varios suscriptores y retorna un ConnectablePublisher, lo que significa que se suscribirá al Publisher de entrada solo cuando reciba la señal connect().
// Subject para emitir eventos del multicast.
// Recibe del flujo de entrada
let subject = PassthroughSubject<Data, URLError>()
// Se crea el Publisher multicast, envolviendo el subject
let multicasted = URLSession.shared
.dataTaskPublisher(for: URL(string: "https://ejemplo.com")!)
.map(\.data)
.multicast(subject: subject)
let subscription1 = multicasted.sink { /* … */ }
var subscription2: AnyCancellable? = nil
// Crear una segunda suscripción al multicast
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
subscription2 = multicasted.sink { /* … */ }
}
// Suscribir el multicast al dataTaskPublisher
DispatchQueue.main.asyncAfter(deadline: .now() + 6) {
let cancellable = multicasted.connect()
}
Como multicast(_:) devuelve un ConnectablePublisher, también se puede usar autoconnect() para que se suscriba al Publisher de entrada tan pronto reciba la primera suscripción.
En escenarios donde solo se requiere hacer un trabajo una vez y emitir un valor, se puede usar CurrentValueSubject.
Future
Future es un Publisher (implementado como class, no como struct) que recibe un closure con un Future.Promise. Ese closure produce un solo valor y luego termina. Dentro del closure se ejecuta el trabajo y, al finalizar, se llama a la promesa con el resultado o con un error.
- Al crear la instancia de
Future, el closure se ejecuta inmediatamente, sin esperar a ningún suscriptor. Futurealmacena el resultado de la promesa y lo emite a todos sus suscriptores, actuales y futuros.
let future = Future { fulfill in
do {
let result = try performSomeWork()
fulfill(.success(result))
} catch {
fulfill(.failure(error))
}
}
let subscription1 = future.sink(
receiveCompletion: { _ in },
receiveValue: { value in print("1️⃣", value) }
)
let subscription2 = future.sink(
receiveCompletion: { _ in },
receiveValue: { value in print("2️⃣", value) }
)
Preguntas de repaso
Publishers de Combine, ¿cada suscripción repite el trabajo del flujo?
- Porque los Publishers son clases que mantienen un estado interno.
- Porque Combine crea un nuevo hilo por suscripción.
- Porque los Publishers son estructuras (
struct) que solo describen el pipeline y no comparten estado. - Porque los suscriptores deben conectarse manualmente con
connect().
share() en un Publisher
- Crea copias separadas del flujo para cada suscriptor.
- Cancela las suscripciones duplicadas.
- Crea una sola suscripción al Publisher original y comparte sus resultados entre varios suscriptores.
- Convierte el Publisher en un
Subject.
Publisher compartido con share() se realiza después de que el flujo ya terminó
- Recibe los valores anteriores almacenados.
- No recibe nada, solo el evento de finalización.
- Se vuelve a ejecutar todo el flujo original.
- Causa un error en tiempo de ejecución.
share() ¿dónde se usa?
- En
FutureyJust. - En
multicast(_:)yPassthroughSubject. - En
CurrentValueSubject. - En
DispatchQueue.
multicast(_:) frente a share()
-
multicast(_:)no necesita unSubject. -
multicast(_:)devuelve un ConnectablePublisher que requiere llamarse conconnect(). -
multicast(_:)repite el trabajo por cada suscriptor. -
share()permite múltiples conexiones manuales, mientras quemulticast(_:)no.
autoconnect() en un ConnectablePublisher creado con multicast(_:)
- Permite que el Publisher se conecte automáticamente al recibir la primera suscripción.
- Permite cancelar automáticamente las suscripciones.
- Convierte el Publisher en un
Future. - Guarda el resultado para suscriptores futuros.
Future de otros Publishers como share() o multicast(_:)
- Es un Publisher
structsin estado. - Solo emite errores, nunca valores.
- Se ejecuta solo después de recibir una suscripción.
- Es una clase que ejecuta su trabajo inmediatamente al crearse y guarda el resultado para todos los suscriptores.
Resumen rápido
- Publishers en Combine son mayormente
struct→ cada suscripción ejecuta el pipeline de nuevo. share()crea una única suscripción al upstream y la comparte.multicast(_:)necesita unSubjecty, al ser connectable, requiereconnect()(oautoconnect()).Futurees una clase que ejecuta su trabajo al instante y almacena el resultado para cualquier suscriptor futuro.
Preguntas y respuestas sobre share(), multicast(_:) y autoconnect()
| Pregunta | Respuesta |
|---|---|
¿Qué hace share() en un Publisher? | ✅ Crea una sola suscripción al Publisher original y comparte sus resultados entre varios suscriptores. |
¿El Publisher compartido con share() se realiza después de que el flujo ya terminó? | ✅ No recibe nada, solo el evento de finalización. |
share() | ✅ En multicast(_:) y PassthroughSubject. |
¿Cuál es la diferencia entre multicast(_:) y share()? | ✅ multicast(_:) devuelve un ConnectablePublisher que requiere llamarse con connect(). |
¿Qué hace autoconnect() en un ConnectablePublisher creado con multicast(_:)? | ✅ Permite que el Publisher se conecte automáticamente al recibir la primera suscripción. |
¿Qué es un Future de otros Publishers como share() o multicast(_:)? | ✅ Es una clase que ejecuta su trabajo inmediatamente al crearse y guarda el resultado para todos los suscriptores. |
Resumen rápido (repetido)
share()→ Un solo suscriptor interno, comparte valores y solo emite el evento de finalización a los suscriptores externos.multicast(_:)→ Devuelve un ConnectablePublisher; necesitaconnect()(oautoconnect()) para iniciar la transmisión.autoconnect()→ Conecta automáticamente al recibir la primera suscripción.Future→ Ejecuta su trabajo al instante y almacena el resultado para cualquier suscriptor posterior.