Skip to content

Add post about Combine and Subscriber protocol #80

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 168 additions & 0 deletions _posts/2021-08-21-combine.md
Original file line number Diff line number Diff line change
@@ -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 protected]"].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).
147 changes: 147 additions & 0 deletions _posts/2021-08-21-subscriber.md
Original file line number Diff line number Diff line change
@@ -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<Self.Failure>)

}

```

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<Never>) {
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