diff --git a/_posts/2021-08-21-combine.md b/_posts/2021-08-21-combine.md new file mode 100644 index 0000000..12405ed --- /dev/null +++ b/_posts/2021-08-21-combine.md @@ -0,0 +1,168 @@ +--- +title: Combine +category: Combine +layout: post +--- + +# Combine + + +Combine es un framework el cual brinda una manera declarativa de procesar los eventos en una aplicación. En lugar de implementar múltiples delegados o closures, es posible crear una sola cadena de procesos para una fuente de eventos, cada uno de las partes de la cadena es un operador de Combine que realiza distintas acciones en los elementos provenientes de los pasos anteriores. + + +El objetivo de Combine es **introducir un ecosistema que ayude a dar un orden en el mundo de la programación asíncrona.** Apple ha integrado la API de Combine dentro de algunas herramientas de Foundation como lo son Timer y NotificacionCenter, así como algunos frameworks extras como Core Data. + + +Por otro lado, SwiftUI fue diseñado para tener una buena integración con Combine. + + +![Figura 1](https://i.ibb.co/pyXX4gj/sketch1629589353228-1.png) + + + +# Conceptos + + +El framework de combine está compuesto por tres componentes principales: + + +- Publishers + +- Subscribers + +- Operators + + + +## Publishers + + +Lost `Publishers` son tipos de datos que cumplen con el protocolo `Publisher` y emiten valores a lo largo del tiempo , estos valores son recibidos por distintos sujetos. + + +Un `Publisher` puede emitir tres tipos de eventos durante su existencia: + + +1. Un valor del tipo genérico de salida que tenga, puede emitir cero ó más valores. + +2. Un evento determinado. + +3. Un evento determinado con un error del tipo que tenga asociado el publisher. + + +Los publishers cuentan con manejo de errores incorporado, por lo que no es necesario manejar de manera explícita a menos que se tenga un caso especial. + + +El protocolo publisher es genérico para dos tipos de datos: + + - `Publisher.Output` es el tipo de los valores de salida del publisher. Si un publisher tiene asociado un tipo `String`, no podrá emitir valores de otro tipo. + + + + - `Publisher.Failure` es el tipo de error que el publisher puede provocar si falla. El publisher puede no fallar, para este caso en particular se puede emplear `Never` que es un error el cual indica que no fallará. + + +![Figura 2](https://i.ibb.co/1Rfh4pq/sketch1629654318366.png) + +## Operators + + +Los operators/operadores son métodos que se encuentran definidos dentro del protocolo `Publisher` y su retorno es el mismo objeto `Publisher` o un objeto `Publisher` distinto. + + +Estos métodos llamados "operadores" se encuentran desacoplados y permiten la composición, por ello es posible usarlos en conjunto para formar cadenas con lógica compleja que se ejecuta en una sola ejecución de una subscripción. + + +Dichos métodos se enfocan en trabajar con los datos recibidos de los operadores anteriores y brindar su salida al siguiente en la cadena, lo que restringe que alguna otra pieza de código asíncrono entre y modifique los datos con los que se está trabajando. + + +## Subscribers + + +Los subscribers/suscriptores son los eslabones finales de la cadena de subscripción. Toda suscripción termina con un suscriptor. Estos generalmente realizan alguna actividad con los valores emitidos o con los eventos de término que provienen de los publishers. + + +Combine provee dos suscriptores de propósito general que facilitan el trabajar con flujos de datos de manera sencilla de implementar + + +- `sink` permite asociar closures propios que recibirán los valores emitidos por el publisher, así como sus eventos de finalización. Con esto es posible implementar cualquier comportamiento que se desee. + + + ```swift + + [1,2,3].publisher.sink(receiveCompletion:{ + + switch completion { + + case .finished : + + print("Se termino exitosamente") + + case .failure(let error): + + print(error) + + } + + }, + + recieveValue: { value in + + print("Valor recibido: \(value)") + + + } + + ) + + ``` + +- `assign` permite enlazar la salida a alguna propiedad en un modelo o un control de interfaz gráfica para mostrar los datos directamente en la pantalla. + + + ```swift + + struct User{ + + var email:String = "" + + } + + + var user = User() + + ["email@email.com"].publisher.assign(to:\.email, on:user) + + + ``` + + +## Ciclo de vida + + +Cuando se agrega un `subscriber` al final de la cadena de operaciones, esté "activa" al `publisher` que se encuentra al inicio de la cadena. Es necesario hacer énfasis en que ningún `publisher` emitirá valores si no existen `subscribers` que potencialmente puedan recibirlos. + + +Un `Publisher` solo emitirá valores si tiene algún `subscriber` que lo esté "observando" y si este se encuentra dispuesto a recibir dichos valores. Usualmente, esto no será un problema mientras se empleen `sink` y `assign` ya que estos `Subscribers` Siempre están dispuestos a recibir los valores emitidos por el `Publisher`. + + +Este principio de permitir al `Subscriber` controlar la emisión de valores es llamado **Backpressure** y permite que los subscribers no sufran de una congestión de valores durante su existencia. + + +![Figura 1](https://i.ibb.co/DDDpHjj/Sketch1629659392394.png) + + +## Ventajas + + + +- Combine está integrado a nivel del sistema, por lo que ocupa funcionalidades que no se encuentran disponibles públicamente. + + +- Combine Abstrae todas las operaciones asíncronas en tu código en forma de operadores y estos ya están probados evitando escribir más pruebas. + + +- Al emplear el protocolo `Publisher` todas las piezas de código asíncrono la reusabilidad de tu código aumenta aún más. + + +- Los operadores en Combine soportan la composición. Si es necesario crear uno nuevo, este operador podrá ser conectado de inmediato con el resto de combine (plug and play). \ No newline at end of file diff --git a/_posts/2021-08-21-subscriber.md b/_posts/2021-08-21-subscriber.md new file mode 100644 index 0000000..404abbd --- /dev/null +++ b/_posts/2021-08-21-subscriber.md @@ -0,0 +1,147 @@ +--- +title: Subscriber +category: Combine +layout: post +--- +# Subscriber + +`Subscriber` es un protocolo que define los requerimientos para que un tipo pueda recibir entradas de un `Publisher`, junto a los eventos del ciclo de vida. Los tipos de `Input` y `Failure` de de un `Subscriber` deben coincidir con los tipos `Output` y `Failure` del `Publisher` asociado. + +Para realizar la conexión de un `Subscriber` con un `Publisher` es necesario llamar al método `subscribe(_:)` del `Publisher`, justo después de realizar esta llamada el `Publisher` invoca el método `receive(subscription:)` del `Subscriber`. + +En este punto se entrega una instancia de tipo `Subscription` al `Subscriber`, el cual será usado para solicitar valores del `Publisher` o en su defecto cancelar la subscripción. + +Una parte del protocolo `Subscriber` se muestra a continuación. + + +```swift + public protocol Subscriber: CustomCombineIdentifierConvertible { + // 1 + associatedtype Input + + // 2 + associatedtype Failure: Error + + // 3 + func receive(subscription: Subscription) + + // 4 + func receive(_ input: Self.Input) -> Subscribers.Demand + + // 5 + func receive(completion: Subscribers.Completion) + + } + +``` + +1. El tipo de valores que el `Subscriber` puede recibir. +2. El tipo de error que el `Subscriber` puede recibir o `Never` si no se recibe ningún tipo de error. +3. El método `receive(subscription:)` es llamado por el `Publisher` para entregar la suscripción al `Subscriber` +4. El método `recieve(_:)` es llamado por el `Publisher` para enviar valores al `Subscriber`. +5. El método `recieve(completion:)` es llamado por el `Publisher` para notificar al `Subscriber` que ha terminado de producir valores de manera exitosa o debido a un error. + +## Subscription + +`Subscription` es un protocolo que representa la conexión entre un `Subscriber` y un `Publisher`. Cuando el `Subscriber` ha terminado o ya no necesita recibir los valores emitidos por un `Publiser` es momento de cancelar la suscripción para liberar recursos y detener todas las actividades relacionadas a dicha suscripción. + +Las suscripciones regresan una instancia del tipo `AnyCancellable` como un token de cancelación, esto permite cancelar dicha suscripción en cualquier momento que se desee. `AnyCancellable` cumple con el protocolo `Cancellable` el cual requiere el método `cancel()` para ser cancelado. + +Si el método `cancel` no es llamado de forma explícita en una suscripción, continuará hasta que el publisher termine su ciclo de vida, o hasta que la administración de memoria cause que las subscripción sea destruída. Si se destruye la subscripción se cancela automáticamente. + +Una consideración importante es que si el valor de la subscripción no es almacenado en alguna variable esta será cancelada de manera automática cuando el programa salga del contexto donde ésta fue creada. + +![Figura 1](https://i.ibb.co/DDDpHjj/Sketch1629659392394.png) + + +Una parte del protocolo `Subscription` se muestra a continuación: + +```swift + public protocol Subscription: Cancellable,CustomCombineIdentifierConvertible { + func request(_ demand: Subscribers.Demand) + } +``` + +El `Subscriber` llama el método `request(_:)` para indicar que desea recibir más valores, hasta un máximo de números o ilimitado. + + +## Ejemplo de Subscriber personalizado + + +Definimos la clase que será `Publisher`: + +```swift + // 1 + final class IntSubscriber: Subscriber { + // 2 + typealias Input = Int + typealias Failure = Never + + // 3 + func receive(subscription: Subscription) { + subscription.request(.max(3)) + } + // 4 + func receive(_ input: Int) -> Subscribers.Demand { + print("Valor recibido", input) + return .none + } + + // 5 + func receive(completion: Subscribers.Completion) { + print("Notificación de término", completion) + } + } +``` +1. Definición de la clase IntSubscriber. +2. Implementación de los `typealias` para especificar que un `Subscriber` puede recibir `Int` como entradas y no recibirá errores. +3. Implementación de los métodos requeridos. iniciando con el método `receive(subscription:)` el cual es llamado por el `Publisher`. Dentro de este se llama el método `request(_:)` de la suscripción, en este caso se indica que recibirá hasta tres valores. +4. Se imprime cada unos de los valores recibidos y se regresa `.none` lo que indica que el `Subscriber` no incrementa su demanda más allá de la demanda inicial. +5. Se imprime al recibir un evento de término. + + +Una vez que hemos definido nuestro `Subscriber` es posible utilizarlo de la siguiente manera. + + +```swift + let publisher = (1...6).publisher + let subscriber = IntSubscriber() + + publisher.subcribe(subscriber) +``` + +La salida del código anterior será la siguiente: + +```console + Valor recibido 1 + Valor recibido 2 + Valor recibido 3 +``` + +En este caso no se recibió la notificación del evento de término debido a que se especificó al `Publisher` que solo se requerían 3 valores. +Si modificamos el método `recieve(_:)` del `Subscriber` de la siguiente manera podemos obtener más valores. + +```swift + func receive(_ input: Int) -> Subscribers.Demand { + print("Valor recibido", input) + return .unlimited + } +``` + +Para este caso la ejecución producirīa la siguiente salida: + +```console + Valor recibido 1 + Valor recibido 2 + Valor recibido 3 + Valor recibido 4 + Valor recibido 5 + Valor recibido 6 + Notificación de termino finished +``` + + +# Referencias + +https://developer.apple.com/documentation/combine/subscription +Combine: Asynchronous Programming with Swift,Florent Pillet, Shai Mishali, Scott Gardner and Marin Todorov \ No newline at end of file