Uso de Combine en Swift

Combine

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<String, Never>()
let publisher2 = PassthroughSubject<Int, Never>()

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<Int, MyError>(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!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio