En el marco Combine, un publicador es un tipo que se ajusta al protocolo de Publisher
. Es responsable de proporcionar un flujo de valores a los suscriptores. El protocolo Publisher
define dos tipos asociados: Output
y Failure
, que indican los tipos de los valores que puede emitir el publisher y los tipos de errores que puede arrojar, respectivamente.
Un publicador puede emitir uno o más valores a lo largo del tiempo y también puede completarse o fallar. Cuando un suscriptor se suscribe a un editor, el editor llama al método receive(subscription:)
del suscriptor y le pasa un objeto Subscription
, que el suscriptor puede usar para controlar el flujo de valores. El suscriptor también puede llamar al método receive(_:)
en el publicador para recibir nuevos valores.
El marco Combine proporciona una serie de publicadores integrados, como Just
, Fail
, Empty
, Deferred
y Sequence
, que se pueden usar para crear varios tipos de publicadores. Además, puede crear sus propios publicadores personalizados conforme al protocolo de Publisher
e implementando los métodos requeridos.
Los editores también se pueden componer juntos para crear canalizaciones más complejas. El marco Combine proporciona una serie de operadores integrados que se pueden usar para modificar y combinar editores, como map
, filter
, reduce
, flatMap
, zip
y merge
. Estos operadores los proporciona la extensión del protocolo Publisher
y se pueden llamar en cualquier editor.
Ahora me gustaría ofrecerle algunos editores útiles que utilizo en mis proyectos.
Para implementar un editor que usa un temporizador repetitivo con un intervalo personalizado en Swift, puede usar la clase Timer
del marco Foundation. Aquí tienes un ejemplo de cómo puedes hacerlo:
RepeatingTimeSubscription
cumple con el protocolo de Subscription
:
private class RepeatingTimerSubscription<S: Subscriber>: Subscription where S.Input == Void { private let interval: TimeInterval private let queue: DispatchQueue private var subscriber: S? private var timer: Timer? init(interval: TimeInterval, queue: DispatchQueue, subscriber: S) { self.interval = interval self.queue = queue self.subscriber = subscriber } func request(_ demand: Subscribers.Demand) { timer?.invalidate() timer = Timer.scheduledTimer( withTimeInterval: interval, repeats: true ) { [weak self] _ in self?.queue.async { _ = self?.subscriber?.receive() } } } func cancel() { timer?.invalidate() timer = nil subscriber = nil } }
RepeatingTimePublisher
cumple con el protocolo de Publisher
:
import Foundation import Combine final class RepeatingTimerPublisher: Publisher { typealias Output = Void typealias Failure = Never private let interval: TimeInterval private let queue: DispatchQueue init(interval: TimeInterval, queue: DispatchQueue = .main) { self.interval = interval self.queue = queue } func receive<S>(subscriber: S) where S: Subscriber, Failure == S.Failure, Output == S.Input { let subscription = RepeatingTimerSubscription( interval: interval, queue: queue, subscriber: subscriber ) subscriber.receive(subscription: subscription) } }
Para utilizar este publicador, puede crear una instancia del mismo y suscribirse mediante el método sink
del protocolo del Publisher
.
Por ejemplo:
private var cancellable: AnyCancellable? func subscribeOnTimer(interval: TimeInterval) { let publisher = RepeatingTimerPublisher(interval: interval) cancellable = publisher.sink { print("Timer fired!") } } //TEST THE METHOD subscribeOnTimer(interval: 5.0)
Esto imprimirá "¡Temporizador disparado!" cada 5 segundos. Puede cancelar la suscripción llamando al método de cancel
en el objeto AnyCancellable
que devuelve el método sink
.
Por ejemplo:
deinit { cancellable?.cancel() }
Para implementar un sondeo largo utilizando el marco Combine en Swift, puede crear un editor que realice una solicitud de red en un intervalo específico y devuelva la respuesta como salida. Aquí tienes un ejemplo de cómo puedes hacerlo:
Enumeración de error personalizada para casos fallidos.
enum CustomError: Error { case invalidResponse case invalidDecoding case error }
LongPollingSubscription
cumple con el protocolo de Subscription
:
private class LongPollingSubscription<S: Subscriber, Output: Decodable>: Subscription where S.Input == Output, S.Failure == CustomError { private let url: URL private let interval: TimeInterval private let decoder: JSONDecoder private var subscriber: S? private var timer: Timer? private var task: URLSessionDataTask? init( url: URL, interval: TimeInterval, subscriber: S, decoder: JSONDecoder = JSONDecoder() ) { self.url = url self.interval = interval self.subscriber = subscriber self.decoder = decoder } func request(_ demand: Subscribers.Demand) { timer?.invalidate() timer = Timer.scheduledTimer( withTimeInterval: interval, repeats: true ) { [weak self] _ in self?.makeRequest() } makeRequest() } func cancel() { timer?.invalidate() timer = nil task?.cancel() task = nil subscriber = nil } private func makeRequest() { task = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in guard let self else { return } if let error = error as? S.Failure { self.subscriber?.receive( completion: .failure(error) ) return } guard let data else { self.subscriber?.receive( completion: .failure(.invalidResponse) ) return } do { let output = try self.decoder.decode( Output.self, from: data ) _ = self.subscriber?.receive(output) } catch { self.subscriber?.receive( completion: .failure(.invalidDecoding) ) } } task?.resume() } }
LongPollingPublisher
cumple con el protocolo de Publisher
:
final class LongPollingPublisher<Output: Decodable>: Publisher { typealias Failure = CustomError private let url: URL private let interval: TimeInterval init(url: URL, interval: TimeInterval) { self.url = url self.interval = interval } func receive<S>(subscriber: S) where S: Subscriber, Failure == S.Failure, Output == S.Input { let subscription = LongPollingSubscription( url: url, interval: interval, subscriber: subscriber ) subscriber.receive(subscription: subscription) } }
<Output: Decodable>
significa que puede usar cualquier tipo genérico de respuesta que se ajuste al protocolo Decodable
.
Para las pruebas, debe crear un modelo que se ajuste a Decodable
. Uso una API pública de https://pixabay.com/api .
Que sea la estructura PhotoResponse:
struct PhotoResponse: Decodable { struct Photo: Decodable { let user: String let id: Int let largeImageURL: String } let hits: [Photo] let total: Int }
Para utilizar este publicador, puede crear una instancia del mismo y suscribirse mediante el método sink
del protocolo del Publisher
. Por ejemplo:
private var cancellable: AnyCancellable? private func pollingTest() { let url = URL(string: "https://pixabay.com/api/?key={your_key}")! let publisher = LongPollingPublisher<PhotoResponse>( url: url, interval: 5.0 ) cancellable = publisher.sink(receiveCompletion: { completion in switch completion { case .finished: print("Completed") case .failure(let error): print("Error: \(error)") } }, receiveValue: { response in print("Received response: \(response)") }) } //TEST THE METHOD pollingTest()
Hay una serie de editores personalizados útiles que se pueden crear utilizando el marco Combine en Swift. Aquí están algunos ejemplos:
NotificationCenterPublisher
: un publicador que emite valores cuando una notificación específica se publica en el NotificationCenter
. Puede usar este publicador para reaccionar ante eventos específicos del sistema o de la aplicación, como la rotación de un dispositivo o un cambio de estado de la red.KeyboardPublisher
: un editor que emite valores cuando el teclado se muestra u oculta. Puede usar este editor para ajustar el diseño de sus vistas cuando se presenta o descarta el teclado.CoreLocationPublisher
: un editor que emite valores cuando cambia la ubicación del usuario. Este editor se puede utilizar para rastrear la ubicación del usuario y realizar acciones en función de su ubicación.UIControlEventPublisher
: un editor que emite valores cuando ocurre un evento específico en un UIControl
como UIButton o UITextField. Esto se puede utilizar para manejar las interacciones del usuario de forma reactiva.Estos son solo algunos ejemplos de los tipos de editores personalizados que se pueden crear utilizando el marco Combine. La clave es comprender los requisitos de su aplicación y utilizar los componentes básicos disponibles proporcionados por el marco para crear editores que cumplan con esos requisitos.
Finalmente, es importante tener en cuenta que el marco Combine utiliza el paradigma de programación reactiva funcional, que es un modelo de programación que trata con flujos de eventos a lo largo del tiempo. Los editores, junto con los suscriptores y los operadores, son los componentes básicos de este paradigma y facilitan la creación de aplicaciones complejas y receptivas.