Combine es un framework introducido por Apple en iOS 13, macOS 10.15, watchOS 6, y tvOS 13, diseñado para facilitar el manejo de eventos y datos asincrónicos en tiempo real. Este framework sigue un patrón reactivo que te permite trabajar con flujos de datos que cambian con el tiempo, utilizando publicadores (publishers) y suscriptores (subscribers) como componentes clave.
En esta entrada de blog, exploraremos cómo funciona Combine, las bases de su arquitectura, y algunos ejemplos prácticos para entender cómo aplicar este framework en tus aplicaciones Swift.
Arquitectura de Combine
En Combine, los dos conceptos clave son:
1. Publicador (Publisher): Un publicador emite eventos o datos a lo largo del tiempo.
2. Suscriptor (Subscriber): Un suscriptor recibe los eventos emitidos por el publicador y realiza una acción.
A través de operadores, es posible transformar, filtrar, combinar o retrasar estos eventos de manera similar a cómo se usan los operadores en una cadena de datos (map, filter, reduce, etc.).
Publicadores y Suscriptores
Un publicador es cualquier fuente que emita datos. Los publicadores más comunes en Combine son los tipos nativos, como URLSession, NotificationCenter y Timer, que ya están conformados para trabajar como publicadores.
Un suscriptor es el receptor de los datos. Los suscriptores reciben datos de un publicador, ya sea mediante un sink o un assign, que son los métodos que manejan la recepción de datos.
Ejemplo básico: Just y sink
Comencemos con un ejemplo básico que utiliza Just, un publicador que emite un solo valor y completa inmediatamente.
import Combine
let justPublisher = Just("¡Hola, Combine!")
let subscription = justPublisher.sink { value in
print("Valor recibido: \(value)")
}
En este código:
•Just es un publicador que emite el valor “¡Hola, Combine!” y luego termina.
•sink es un suscriptor que simplemente imprime el valor recibido.
La salida será:
Valor recibido: ¡Hola, Combine!
Publicadores asíncronos: URLSession y Combine
Uno de los usos más comunes de Combine es la realización de solicitudes de red asíncronas. Supongamos que queremos realizar una solicitud HTTP utilizando URLSession y manejar la respuesta con Combine.
import Combine
import Foundation
// URL para hacer una solicitud
let url = URL(string: "https://jsonplaceholder.typicode.com/posts/1")!
// Creamos un publicador desde la llamada a URLSession
let publisher = URLSession.shared.dataTaskPublisher(for: url)
.map { $0.data } // Extraemos los datos
.decode(type: Post.self, decoder: JSONDecoder()) // Decodificamos los datos en un objeto Post
.eraseToAnyPublisher() // Convertimos el tipo a AnyPublisher para mayor flexibilidad
// Suscribimos y gestionamos el resultado
let subscription = publisher
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
print("Solicitud completada.")
case .failure(let error):
print("Error: \(error.localizedDescription)")
}
}, receiveValue: { post in
print("Título del post: \(post.title)")
})
// Modelo que decodificamos
struct Post: Decodable {
let userId: Int
let id: Int
let title: String
let body: String
}
En este ejemplo:
•dataTaskPublisher(for:) convierte la solicitud de red en un publicador que emite los datos de la respuesta.
•Utilizamos map para obtener solo los datos (data).
•Luego, decode(type:decoder:) convierte esos datos en una instancia de nuestro modelo Post usando JSONDecoder.
•eraseToAnyPublisher() se utiliza para ocultar el tipo concreto del publicador, permitiendo mayor flexibilidad en las suscripciones.
Uso de Operadores: Transformaciones con map, filter y más
Los operadores en Combine permiten transformar y filtrar los valores que emite un publicador, de manera similar a las funciones de orden superior en Swift. Aquí un ejemplo con varios operadores.
import Combine
let numbers = (1...10).publisher
let subscription = numbers
.filter { $0 % 2 == 0 } // Solo pares
.map { $0 * 2 } // Multiplicamos los pares por 2
.sink { value in
print("Valor transformado: \(value)")
}
Este código emite solo los números pares del 1 al 10 y luego los multiplica por 2. La salida sería:
Valor transformado: 4
Valor transformado: 8
Valor transformado: 12
Valor transformado: 16
Valor transformado: 20
Combinar múltiples publicadores
En ocasiones, es necesario combinar datos provenientes de diferentes publicadores. Combine facilita esta tarea mediante operadores como combineLatest, merge y zip.
Ejemplo con combineLatest
import Combine
let publisher1 = PassthroughSubject()
let publisher2 = PassthroughSubject()
let subscription = publisher1
.combineLatest(publisher2)
.sink { string, number in
print("Valor combinado: \(string), \(number)")
}
// Emitimos valores
publisher1.send("Valor A")
publisher2.send(1)
publisher1.send("Valor B")
publisher2.send(2)
Salida:
Valor combinado: Valor A, 1
Valor combinado: Valor B, 1
Valor combinado: Valor B, 2
En este caso, combineLatest combina el valor más reciente de ambos publicadores y emite un par de valores cada vez que cualquiera de los publicadores emite un nuevo valor.
Control de errores con Combine
El manejo de errores es fundamental en cualquier programación reactiva. Combine ofrece operadores como catch y retry para gestionar errores.
import Combine
enum MyError: Error {
case unknown
}
let failPublisher = Fail(error: .unknown)
let subscription = failPublisher
.catch { error in
return Just(0) // Retornamos un valor por defecto en caso de error
}
.sink { value in
print("Valor recibido tras el error: \(value)")
}
Salida:
Valor recibido tras el error: 0
En este ejemplo, cuando el publicador falla, catch permite interceptar el error y proporcionar un valor alternativo, en este caso 0.
Conclusión
Combine ofrece una manera poderosa y flexible de manejar flujos de datos asíncronos en tus aplicaciones Swift. Desde transformar y filtrar eventos hasta combinar múltiples flujos y manejar errores, el framework te proporciona las herramientas necesarias para construir aplicaciones más reactivas y eficientes. Con esta introducción, ya tienes una base sólida para comenzar a usar Combine en tus proyectos.
Recursos adicionales
•Documentación oficial de Combine
•Ejemplos de código en Swift con Combine
Con esto puedes comenzar a experimentar con Combine en tus proyectos. Si tienes dudas o comentarios, no dudes en dejarme saber. ¡Feliz codificación!