 Combine
Combine es un marco funcional 
Swift reactivo que se ha implementado recientemente para todas 
Apple plataformas de 
Apple , incluido 
Xcode 11 . Con 
Combine, es muy fácil procesar secuencias de valores que aparecen de forma asincrónica a lo largo del tiempo. También simplifica el código asincrónico al abandonar la delegación y las 
devoluciones de llamada complejas anidadas.
Pero estudiar 
Combinar en sí mismo al principio puede no parecer tan simple. El hecho es que los principales "jugadores" de 
Combine son conceptos tan abstractos como "editores" 
Editores , "suscriptores" 
Suscriptores y operadores 
Operadores , sin los cuales no será posible avanzar en la comprensión de la lógica de funcionamiento de 
Combine . Sin embargo, debido al hecho de que 
Apple proporciona a los desarrolladores "editores", "suscriptores" y operadores ya preparados, el código escrito con 
Combine es muy compacto y fácil de leer.
Verá esto en el ejemplo de una aplicación relacionada con la 
recuperación asincrónica de información de 
películas de la muy popular base de datos 
TMDb ahora . Crearemos dos aplicaciones diferentes: 
UIKit y 
SwiftUI , y mostraremos cómo funciona 
Combine con ellas.

Espero que este artículo te facilite aprender 
Combinar . El código para todas las aplicaciones desarrolladas en este artículo se puede encontrar en 
Github .
Combine tiene varios componentes principales:
Editor Editor .

Editores Los 
editores son TIPOS que entregan 
valores a todos los que se preocupan. 
El concepto de "editor" del editor se implementa en 
Combine como un 
protocolo , no como un TIPO específico. El protocolo del 
editor tiene TIPOS genéricos asociados para el valor de salida y el error de 
falla .
Un "editor" que nunca publica un error utiliza el error TIPO 
Nunca para un error.


Apple proporciona a los desarrolladores implementaciones específicas de "editores" ya preparados: 
Just , 
Future , 
Empty , 
Deferred , 
Sequence , 
@Published , etc. Los "editores" también se agregan a las clases de 
Foundation : 
URLSession , < 
NotificationCenter , 
Timer .
"Suscriptor" Suscriptor .

También es un protocolo de 
protocolo que proporciona una interfaz para "suscribirse" a los 
valores de un "editor". Ha asociado TIPOS genéricos para errores de 
entrada y 
falla . Obviamente, los TIPOS del publicador 
editor y 
suscriptor deben coincidir.

Para cualquier editor de editor, hay dos 
suscriptores integrados: 
hundir y 
asignar :

El 
sumidero "Suscriptor" 
se basa en dos cierres: un cierre, 
recibirValor , se ejecuta cuando obtiene los 
valores , el segundo cierre, 
recibirCompleción , se ejecuta cuando se completa la "publicación" (normalmente o con un error).

La 
asignación "Suscriptor", avanza cada valor recibido, hacia la 
key Path especificada.
Suscripción "Suscripción".

Primero, el 
Editor "editor" crea y entrega la Suscripción " 
Suscripción " al Suscriptor " 
Suscriptor " a través de su método de 
recepción (suscripción :) :

Después de eso, la 
Suscripción puede enviar sus 
valores a los "suscriptores" de los 
Suscriptores utilizando dos métodos:

Si ha terminado de trabajar con la 
Suscripción Suscripción , puede llamar a su método 
cancel () :

Asunto "Asunto".

Este es un protocolo de 
protocolo que proporciona una interfaz para ambos clientes, tanto para el "editor" como para el "suscriptor". Esencialmente, el Asunto "sujeto" es el 
Editor "editor", que puede aceptar el valor de entrada 
Entrada y que puede usar para "inyectar" los 
valores en la secuencia llamando al método 
send () . Esto puede ser útil al adaptar el código imperativo existente en 
Combine Models.
Operador Operador .

Con el operador, puede crear un nuevo "editor" de editor a partir de otro "editor" de editor convirtiendo, filtrando e incluso combinando los 
valores de los muchos 
publicadores anteriores.

Aquí puede ver muchos nombres de operadores conocidos: 
compactMap , 
map , 
filter , 
dropFirst , 
append .
Editores de la Fundación Editores integrados en la Fundación .
Apple también proporciona a los desarrolladores varias de las funciones 
combinadas ya integradas en el marco 
Foundation <, es decir, los editores de los editores, para tareas como obtener datos usando 
URLSession , trabajar con notificaciones usando 
Notification , 
Timer y monitorear propiedades basadas en 
KVO . Esta compatibilidad incorporada realmente nos ayudará a integrar el marco 
Combine en nuestro proyecto actual.
Para obtener más información sobre esto, consulte el artículo 
"El último tutorial de marco de combinación en Swift" .
¿Qué aprendemos a hacer con Combine ?
En este artículo, aprenderemos cómo usar el marco 
Combinar para obtener datos de películas del 
sitio web 
TMDb . Esto es lo que estudiaremos juntos:
- Uso del futuro "editor" para crear un cierre con Promise para un solo valor: valor o error.
- Uso de la URLSession.datataskPublisher de "editor" para "suscribirse" a los datos de datos publicados por un URL.
- Usando el operador tryMap para convertir datos de datos usando otro editor Publisher.
- Usar el operador de decodificación para convertir datos de datos en un objeto decodificable y publicarlos para su transmisión a elementos posteriores de la cadena.
- Uso del operador de sumidero para "suscribirse" a un editor "editor" mediante cierres.
- Utilice la instrucción de asignación para "suscribirse" al editor "publicador" y asigne el valor que proporciona a la key Pathdada.
Proyecto inicial
Antes de comenzar, debemos registrarnos para recibir la clave 
API en el 
sitio web 
TMDb . También debe descargar el proyecto inicial desde el repositorio de 
GitHub .
Asegúrese de colocar su clave 
API en la 
clase MovieStore clase en 
let apiKey constante.

Estos son los principales bloques de construcción a partir de los cuales crearemos nuestro proyecto:
- 1. Dentro del archivo Movie.swifthay modelos que utilizaremos en nuestro proyecto. La estructura raíz de struct MoviesResponse implementa el protocolo Decodable , y lo usaremos cuando decodifiquemos datosJSONen un Modelo. La estructura MoviesResponse tiene una propiedad de resultados , que también implementa el protocolo Decodable y es una colección de películas [Movie] . Es ella quien nos interesa:

- 2. Enumeración de enumeración MovieStoreAPIError implementa el protocolo de error . Nuestra APIutilizará esta enumeración para representar varios tipos de errores: errores de recuperación deURLurlError , errores de decodificación de decodificación de error y errores de recuperación de datos de respuestaError .

- 3. Nuestra APItiene un protocolo MovieService con un único método, fetchMovies (from endpoint: Endpoint) , que selecciona películas [Movie] en función del parámetro de punto final . El punto final en sí mismo es un enum enum que representa un punto final para acceder a la base de datos TMDb para buscar películas como nowPlaying (más reciente), popular (popular), topRated (arriba) y próximamente (próximamente en la pantalla).

- 4. La clase MovieStore es una clase específica que implementa el protocolo MovieService para obtener datos del sitio TMDb . Dentro de esta clase, implementamos el método fetchMovies (...) usando Combine .

- 5. La clase MovieListViewController es la clase principal de ViewController en la que utilizamos el método de sumidero para "suscribirnos" al método fetchMovies (...) película, que devuelve el "publicador" Futuro , y luego actualiza la tabla TableView con nuevos datos de película películas con la nueva APIDiffableDataSourceSnapshot .
Antes de comenzar, veamos algunos de los componentes básicos de 
Combine que usaremos para 
API recuperación remota de datos.
"Suscríbete" al "editor" usando el sumidero y sus cierres.
La forma más fácil de "suscribirse" al "editor" del editor es utilizar 
sumidero con sus cierres, uno de los cuales se ejecutará cada vez que obtengamos un nuevo 
valor , y el otro cuando el "editor" termine de entregar los 
valores .

Recuerde que en 
Combine, cada "suscripción" devuelve un 
Cancelable , que se eliminará tan pronto como abandonemos nuestro contexto. Para mantener una "suscripción" durante más tiempo, por ejemplo, para obtener valores de forma asincrónica, necesitamos guardar la "suscripción" en la propiedad 
subscribing1 . Esto nos permitió obtener consistentemente todos los 
valores (7,8,3,4) .
Futuro asincrónicamente "publica" un único valor: valor o error de falla .
En el marco 
Combinar , el 
futuro "editor" se puede utilizar para obtener de forma asincrónica un TIPO de 
resultado único mediante un cierre. El cierre tiene un parámetro: 
Promesa , que es una función de TIPO 
(Resultado <Salida, Falla>) -> Anulado .
Veamos un ejemplo simple para entender cómo funciona el 
futuro editor:

Creamos 
Future con un resultado exitoso de TYPE 
Int y un error de TYPE 
Never . Dentro del cierre 
Future , utilizamos 
DispatchQueue.main.asyncAfter (...) para retrasar la ejecución del código en 
2 segundos, simulando así un comportamiento asincrónico. Dentro del cierre, devolvemos 
Promise con un resultado exitoso de 
promesa (.success (...)) en forma de un valor entero aleatorio de 
Int en el rango entre 
0 y 
100 . A continuación, usamos dos suscripciones 
futuras , 
cancelable y 
cancelable1 , y ambas dan el mismo resultado, aunque se genera un número aleatorio en el interior.
1. Cabe señalar que el "editor" de Future in Combine tiene algunas características de comportamiento en comparación con otros "editores":
- El "editor" Futuro siempre "publica" UN valor ( valor o error), y esto completa su trabajo.
- Future "publisher" es una clase de clase ( reference type) , a diferencia de otros "publishers", que son principalmente estructuras de estructura (value type), y recibe el cierre Promise , que se crea inmediatamente después de la inicialización de la instancia Future "publisher", como parámetro. Es decir, el cierre de Promise se transmite antes de que cualquier suscriptor " suscriptor " se suscriba a la instancia de "editor" del Futuro . El "editor" de Future no requiere un "suscriptor" para su funcionamiento, como requieren todos los demás Editores comunes. Es por eso que el texto "¡Hola desde el futuro!" Se imprime solo una vez en el código anterior.
- El "editor" futuro es un "editor" eager(impaciente), a diferencia de la mayoría de los "publicadores" perezosos ("publicar" solo si hay una "suscripción"). Solo una vez que el editor del Futuro cierra su Promesa , el resultado se recuerda y luego se entrega a los "suscriptores" actuales y futuros. Del código anterior, vemos que cuando se "suscribe" repetidamente al futuro editor, siempre obtiene el mismo valor "aleatorio" (en este caso 6 , pero puede ser diferente, pero siempre el mismo), aunque se usa en el cierre valor int aleatorio.
Dicha lógica del "editor" de 
Future permite que se use con éxito para almacenar un resultado calculado asincrónico que consume recursos y no perturbar el "servidor" para las subsiguientes "suscripciones" múltiples.
Si esa lógica del "editor" de 
Future no le conviene y desea que su 
Future se llame 
flojo y cada vez que obtenga nuevos valores 
Int aleatorios, entonces debe "envolver" 
Future en 
diferido :

Usaremos 
Future de una manera clásica, como se sugiere en 
Combine , es decir, como un "editor" asincrónico computarizado "compartido".
Nota 2. Necesitamos hacer un comentario más con respecto a la "suscripción" que usa sumidero para el "Editor" asíncrono. El método de hundimiento devuelve AnyCancellable , que no recordamos constantemente. Esto significa que Swift destruirá AnyCancellable cuando abandone el contexto dado, que es lo que sucede en el main thread . Por lo tanto, resulta que AnyCancellable se destruye antes de que el cierre con Promise pueda comenzar en el main thread . Cuando se destruye AnyCancellable , se llama a su método de cancelación , que en este caso cancela la "suscripción". Es por eso que recordamos nuestras "suscripciones" de sumidero para el futuro en las variables cancelable <y cancelable1 o en Set <AnyCancellable> () .
Uso de Combinar para buscar películas del sitio web TMDb .
Para comenzar, abra el proyecto de inicio y vaya al archivo 
MovieStore.swift y al método 
fetchMovies con una implementación vacía:

Usando el método 
fetchMovies, podemos seleccionar varias películas estableciendo valores específicos para el 
parámetro de entrada de 
punto final de TYPE 
Endpoint . TYPE 
Endpoint es una 
enumeración de enumeración que toma los valores 
nowPlaying (actual), 
próxima (próximamente en la pantalla), 
popular (popular), 
topRated (superior):

Comencemos inicializando 
Future con un cierre de 
callback . Recibido 
Futuro , luego lo reembolsaremos.

Dentro del cierre de 
callback , generamos la 
URL para el valor correspondiente del 
parámetro de entrada de 
punto final utilizando la función 
generateURL (con punto final: Punto final) :

Si no se pudo generar la 
URL correcta, entonces devolvemos un error usando la 
promesa (.failure (.urlError (...)) , de lo contrario seguimos adelante e implementamos la 
URLSession.dataTaskPublisher "publicador".
Para "suscribirse" a los datos de una determinada 
URL podemos utilizar el método 
datataskPublisher integrado en la clase 
URLSession , que toma la 
URL como parámetro y devuelve el "editor" del editor con los datos de salida de la tupla 
(datos: datos, respuesta: URLResponse) y un error 
URLError .

Para convertir un editor de Publisher a otro editor de Publisher, use el operador 
tryMap . En comparación con el 
mapa , el operador 
tryMap puede arrojar un error 
Error de lanzamiento dentro de un cierre, lo que nos devuelve el nuevo editor Publisher.
En el siguiente paso, utilizaremos el operador 
tryMap para verificar el código 
http código de estado de la respuesta de 
respuesta para asegurarnos de que su valor esté entre 
200 y 
300 . Si no, entonces arrojamos 
arroja el valor de error de enumeración 
responseError enum MovieStoreAPIError . De lo contrario (cuando no hay errores), simplemente devolvemos los datos de 
datos recibidos 
al siguiente 
editor de la cadena "editor".

En el siguiente paso, utilizaremos el operador de 
decodificación , que decodifica los datos 
JSON salida del 
tryMap del "editor" 
anterior en el 
MovieResponse <Modelo usando 
JSONDecoder .

... 
jsonDecoder está configurado para un formato de fecha específico:

Para que el procesamiento se realice en el 
main thread , utilizaremos el operador de 
recepción (on :) y pasaremos 
RunLoop.main como su parámetro de entrada. Esto permitirá que el "suscriptor" obtenga el valor del 
valor en el hilo 
main .

Finalmente, llegamos al final de nuestra cadena de transformación, y allí usamos 
sumidero para obtener una suscripción de " 
suscripción " a la "cadena" formada de "editores" de Editores. Para inicializar una instancia de la clase 
Sink , necesitamos dos cosas, aunque una de ellas es opcional:
- Cierre de recibir Valor :. Se llamará siempre que una suscripción de "suscripción" reciba un nuevo valor del editor "editor".
- Recibo Cierre de finalización: (Opcional). Se llamará después de que el editor "editor" haya terminado de publicar el valor , se le dará la enumeración de finalización , que podemos usar para verificar si la "publicación" de los valores se completó realmente o si la finalización se debió a un error.
Dentro del cierre de 
recibenValor , simplemente 
invocamos una promesa con una opción 
.success y un valor de 
$ 0.resultados , que en nuestro caso es una variedad de 
películas . Dentro del cierre de 
complete la 
recepción, verificamos si la 
finalización tiene un error de 
error , luego pasamos el error de 
promesa correspondiente con la opción 
.failure .

Tenga en cuenta que aquí recopilamos todos los errores "descartados" en las etapas anteriores de la "cadena de editores".
A continuación, memorizamos la suscripción de "suscripción" en la propiedad 
Set <AnyCancellable> () .
El hecho es que la suscripción de "suscripción" es 
Cancelable , es un protocolo que destruye y borra todo después de completar la función 
fetchMovies . Para garantizar la preservación de la suscripción de "suscripción" incluso después de completar esta función, debemos recordar la suscripción de " 
suscripción " en la variable externa a la función 
fetchMovies . En nuestro caso, utilizamos la propiedad de 
suscripciones , que tiene el tipo 
Set <AnyCancellable> () y utilizamos el 
método .store (en: & self.subscriptions) , que garantiza la 
usabilidad de la "suscripción" después de que la función 
fetchMovies finalice su trabajo.

Esto concluye la formación del método 
fetchMovies para seleccionar películas de la 
base de datos 
TMDb usando el marco 
Combinar . El método 
fetchMovies , como parámetro de entrada 
de, toma el valor de la 
enumeración Endpoint de enumeración, es decir, qué películas específicas nos interesan:
.nowPlaying : películas que están actualmente en pantalla,
.upcoming : películas que 
llegarán pronto,
.popular - películas populares,
.topRated - mejores películas, es decir, con una calificación muy alta.
Intentemos aplicar esta 
API al diseño de la aplicación con las interfaces de usuario 
UIKit habituales en forma de un 
Table View Controller :

y para una aplicación cuya interfaz de usuario se 
crea utilizando el nuevo marco declarativo 
SwiftUI :

"Suscribirse" a películas de las películas habituales de View Controller .
Nos movemos al archivo 
MovieListViewController.swift y en el método 
viewDidLoad llamamos al método 
fetchMovies .

Dentro de nuestro método 
fetchMovies, utilizamos el 
movieAPI desarrollado anteriormente y su método 
fetchMovies con el parámetro 
.nowPlaying como 
punto final del parámetro de entrada 
from . Es decir, elegiremos las películas que se encuentran actualmente en las pantallas de los cines.

El 
método movieAPI.fetchMovies (de: .nowPlaying) devuelve 
Future "publisher", al que nos "suscribimos" mediante 
sumidero , y le suministramos dos cierres. En el cierre de 
complete la 
recepción , verificamos si hay un error de 
error y mostramos una advertencia de emergencia a la 
alerta del usuario con el mensaje de error mostrado.

En el cierre de 
recibenValor, llamamos al método 
generateSnapshot y le pasamos las 
películas seleccionadas.

La función 
generateSnapshot genera un nuevo 
NSDiffableDataSourceSnapshot usando nuestras 
películas y aplica la 
instantánea resultante al 
diffableDataSource de nuestra tabla.
Lanzamos la aplicación y observamos cómo funciona 
UIKit con los "editores" y los "suscriptores" desde el marco 
Combine . Esta es una aplicación muy simple que no le permite sintonizar varias colecciones de películas, que ahora se muestran en la pantalla, populares, de alta calificación o las que aparecerán en la pantalla en el futuro cercano. Solo vemos las películas que van a aparecer en la pantalla ( 
próximamente ). Por supuesto, puede hacer esto agregando cualquier elemento de la 
UI para establecer los valores de la enumeración de 
Endpoint , por ejemplo, usando 
Stepper o 
Segmented Control , y luego actualizar la interfaz de usuario. Esto es bien conocido, pero no lo haremos en una 
aplicación basada en 
UIKit , sino que lo dejaremos en el nuevo marco declarativo 
SwiftUI .
El código para una 
aplicación basada en 
UIKit se puede encontrar en 
Github en la 
CombineFetchAPICompleted-UIKit .
Use SwiftUI para mostrar películas
.
Creamos una nueva aplicación CombineFetchAPI-MYcon la interfaz SwiftUI usando el menú File-> New-> Projecty seleccionamos la plantilla Single View Appen la sección iOS: Luego especifique el nombre del proyecto y el método de creación
Luego especifique el nombre del proyecto y el método de creación UI- SwiftUI : Luego, especifique la ubicación del proyecto y copie el archivo Modelo al nuevo proyecto
Luego, especifique la ubicación del proyecto y copie el archivo Modelo al nuevo proyecto Movie.swifty colóquelo en la carpeta Modelnecesaria para la interacción con TMDB archivos MovieStore.swift, MovieStoreAPIError.swifty MovieService.swift, en consecuencia y colocarlos en carpetas MovieServicey Protocol: en SwiftUI requería que
en SwiftUI requería que JSONdatos yIdentificable , si queremos facilitar la visualización de la lista de películas [película] en forma de una lista de una lista . Los modelos en SwiftUI no necesitan ser Equatable y Hashable , como lo requiere UIKit API para UITableViewDiffableDataSource en la aplicación UIKit anterior . Por lo tanto, eliminamos de la estructura < struct Movie todos los métodos asociados con los protocolos Equatable y Hashable : ............................
............................ Hay un gran artículo identificable que muestra la diferencia y las similitudes entre los
Hay un gran artículo identificable que muestra la diferencia y las similitudes entre los Swiftprotocolos.Identificable , Hashable y Equatable .La interfaz de usuario que crearemos con SwiftUI se basa en el hecho de que cambiamos el punto final al obtener datos y obtenemos la colección de películas que necesitamos, que se presenta en forma de una lista: así como en el caso de UIKit , los datos se muestrean utilizando la función movieAPI .fetchMovies (desde el punto final: Punto final) , que obtiene el punto final deseado y devuelve el "publicador" Futuro <[Movie, MovieStoreAPIError]> . Si echamos un vistazo a la enumeración de Endpoint , veremos que podemos inicializar la opción deseadacaso en la enumeración de Endpoint y, en consecuencia, la colección de películas deseada utilizando el índice de índice :
así como en el caso de UIKit , los datos se muestrean utilizando la función movieAPI .fetchMovies (desde el punto final: Punto final) , que obtiene el punto final deseado y devuelve el "publicador" Futuro <[Movie, MovieStoreAPIError]> . Si echamos un vistazo a la enumeración de Endpoint , veremos que podemos inicializar la opción deseadacaso en la enumeración de Endpoint y, en consecuencia, la colección de películas deseada utilizando el índice de índice : Por lo tanto, para obtener la colección de películas que necesitamos para películas , simplemente establezca el índice indexEndpoint correspondiente de la enumeración de Endpoint . Hagámoslo
Por lo tanto, para obtener la colección de películas que necesitamos para películas , simplemente establezca el índice indexEndpoint correspondiente de la enumeración de Endpoint . Hagámoslo View Model, que en nuestro caso será la clase MoviesViewModel que implementa el protocolo ObservableObject . Agregue un nuevo archivo MoviesViewModel.swift para el nuestro en nuestro proyecto View Model: en esta clase muy simple, tenemos dos propiedades @Published : una @Published var indexEndpoint: Int- entrada, otras películas Var publicadas: [Película] - salida. Una vez que ponemos @Published propiedad frente indexEndpoint , podemos empezar a utilizarlo como una simple propiedad indexEndpoint , y como editor $ indexEndpoint .Al inicializar una instancia de nuestra clase MoviesViewModel tenemos que estirar la cadena de entrada "editor" $ indexEndpoint de salida de tipo "editor" AnyPublisher <[Película], para Never> , lo que obtenemos mediante el uso de las funciones ya conocidas movieAPI.fetchMovies (de: Punto final (índice : indexPoint)) y el operador flatMap .
en esta clase muy simple, tenemos dos propiedades @Published : una @Published var indexEndpoint: Int- entrada, otras películas Var publicadas: [Película] - salida. Una vez que ponemos @Published propiedad frente indexEndpoint , podemos empezar a utilizarlo como una simple propiedad indexEndpoint , y como editor $ indexEndpoint .Al inicializar una instancia de nuestra clase MoviesViewModel tenemos que estirar la cadena de entrada "editor" $ indexEndpoint de salida de tipo "editor" AnyPublisher <[Película], para Never> , lo que obtenemos mediante el uso de las funciones ya conocidas movieAPI.fetchMovies (de: Punto final (índice : indexPoint)) y el operador flatMap . A continuación, nos "suscribimos" a este "editor" recién recibido utilizando un "suscriptor" muy simple que asigna (a: \ .movies, on: self) y asignamos el valor recibido del "editor" a la matriz de salida de películas . Podemos aplicar la evaluación de "suscripción" (a: \ .movies, on: self) solo si el "editor" no arroja un error, es decir, tiene un TIPO de error Nunca . ¿Cómo lograr esto? Usando el operador replaceError (con: []) , que reemplaza cualquier error con una matriz de películas vacía .Es decir, la primera versión más simple de nuestra aplicación SwiftUI no mostrará información sobre posibles errores al usuario.Ahora que tenemos
A continuación, nos "suscribimos" a este "editor" recién recibido utilizando un "suscriptor" muy simple que asigna (a: \ .movies, on: self) y asignamos el valor recibido del "editor" a la matriz de salida de películas . Podemos aplicar la evaluación de "suscripción" (a: \ .movies, on: self) solo si el "editor" no arroja un error, es decir, tiene un TIPO de error Nunca . ¿Cómo lograr esto? Usando el operador replaceError (con: []) , que reemplaza cualquier error con una matriz de películas vacía .Es decir, la primera versión más simple de nuestra aplicación SwiftUI no mostrará información sobre posibles errores al usuario.Ahora que tenemos View Modelpara nuestras películas, comencemos a crear UI. Añadir al archivo ContentView.swift nuestra View Modelforma @EnvironmentObject variables var moviesViewModel y reemplazar texto ( «¡Hola, mundo!» ) Enel texto ( "\ (moviesViewModel.indexEndpoint)") , que simplemente muestra el índice indexEndpoint colección variante de película. De manera predeterminada, en nuestro
De manera predeterminada, en nuestro View Modelíndice de colección indexEndpoint = 2 , es decir, al iniciar la aplicación, deberíamos ver películas que aparecerán en la pantalla ( Próximamente ) en un futuro próximo : luego agregue
luego agregueUIElementos para controlar qué colección de películas queremos mostrar. Este es Stepper : ... y Picker :
... y Picker : ambos usan el "editor" $ moviesViewModel.indexEndpoint nuestro
ambos usan el "editor" $ moviesViewModel.indexEndpoint nuestro View Model, y con uno de ellos (de todos modos, cuál) podemos seleccionar la colección de películas que necesitamos: Luego agregamos la lista de películas recibidas usando List y ForEach y atributos mínimos película en sí misma película :
Luego agregamos la lista de películas recibidas usando List y ForEach y atributos mínimos película en sí misma película : Lista de películas mostradas moviesViewModel.movies que también tomamos de las nuestras
Lista de películas mostradas moviesViewModel.movies que también tomamos de las nuestras View Model: NO utilizamos $ publisher $ moviesViewModel.movies con el signo $, porque no vamos a editar nada en esta lista de películas. Usamos la propiedad usual moviesViewModel.movies .Puede hacer que la lista de películas sea más interesante mostrando en cada línea de la lista el póster de la película correspondiente,
NO utilizamos $ publisher $ moviesViewModel.movies con el signo $, porque no vamos a editar nada en esta lista de películas. Usamos la propiedad usual moviesViewModel.movies .Puede hacer que la lista de películas sea más interesante mostrando en cada línea de la lista el póster de la película correspondiente, URLque se presenta en la estructura de la película : Tomamos esta oportunidad de Thomas Ricouard de su hermoso proyecto MovieSwiftUI .Como en el caso de cargar películas , para la imagen UIImage , tenemos el servicio ImageService , que implementa el método fetchImage con Combinedevolviendo el "editor" AnyPublisher <UIImage?, Never> :
Tomamos esta oportunidad de Thomas Ricouard de su hermoso proyecto MovieSwiftUI .Como en el caso de cargar películas , para la imagen UIImage , tenemos el servicio ImageService , que implementa el método fetchImage con Combinedevolviendo el "editor" AnyPublisher <UIImage?, Never> : ... y la clase final ImageLoader: ObservableObject que implementa el protocolo ObservableObject con la imagen de propiedad @Published : UIImage?
... y la clase final ImageLoader: ObservableObject que implementa el protocolo ObservableObject con la imagen de propiedad @Published : UIImage? :
 El único requisito que establece el protocolo ObservableObject es la existencia de la propiedad objectWillChange . SwiftUI usa esta propiedad para comprender que algo ha cambiado en la instancia de esta clase, y tan pronto como esto sucede, actualiza todas las Vistas que dependen de la instancia de esta clase. Normalmente, el compilador crea automáticamente una propiedad objectWillChange , y todas las propiedades @Published también lo notifican automáticamente. En el caso de algunas situaciones exóticas, puede crear manualmente un objectWillChange y notificarle los cambios. Tenemos tal caso.En la clase ImageLoader @Published var image:UIImage?
El único requisito que establece el protocolo ObservableObject es la existencia de la propiedad objectWillChange . SwiftUI usa esta propiedad para comprender que algo ha cambiado en la instancia de esta clase, y tan pronto como esto sucede, actualiza todas las Vistas que dependen de la instancia de esta clase. Normalmente, el compilador crea automáticamente una propiedad objectWillChange , y todas las propiedades @Published también lo notifican automáticamente. En el caso de algunas situaciones exóticas, puede crear manualmente un objectWillChange y notificarle los cambios. Tenemos tal caso.En la clase ImageLoader @Published var image:UIImage? . , 
ImageLoader , «» 
$image «» 
loadImage() , 
poster size @Published var image:UIImage? .
Notificaremos a objectWillChange de estos cambios .Podemos tener muchas de esas imágenes en la tabla, lo que conlleva costos de tiempo significativos, por lo que utilizamos el almacenamiento en caché de las instancias de imageLoader de la clase ImageLoader : tenemos una vista especial para reproducir el póster de la película MoviePosterImage :
tenemos una vista especial para reproducir el póster de la película MoviePosterImage : ... y la usaremos al mostrar una lista de películas en nuestro ContentView principal :
... y la usaremos al mostrar una lista de películas en nuestro ContentView principal :
 El código para la aplicación basada en SwiftUI sin visualización de errores se puede encontrar en Github en la carpeta
El código para la aplicación basada en SwiftUI sin visualización de errores se puede encontrar en Github en la carpeta CombineFetchAPI-NOError.Mostrar errores de recuperación remota de películas asincrónicas.
Hasta el momento, no hemos usado ni mostrado errores que ocurran durante la selección remota asíncrona de películas desde el sitio web TMDb . Aunque la función movieAPI.fetchMovies (from endpoint: Endpoint) que utilizamos nos permite hacer esto, ya que devuelve el "editor" Future <[Movie, MovieStoreAPIError]> .Con el fin de permitir que los errores, añadir a nuestra View Modelotra @Published propiedad moviesError: MovieStoreAPIError? eso representa el error. Esta es una propiedad opcional , su valor inicial es nulo , que corresponde a la ausencia de un error: para obtener este error moviesError, tendremos que cambiar ligeramente la inicialización de la clase MoviesViewModel y usar un suscriptor de sumidero más complicado : el
para obtener este error moviesError, tendremos que cambiar ligeramente la inicialización de la clase MoviesViewModel y usar un suscriptor de sumidero más complicado : el error moviesError se puede mostrar
error moviesError se puede mostrar UIsi no es nulo ... usando AlertView : Simulamos
usando AlertView : Simulamos este error simplemente quitando la
este error simplemente quitando la APIclave correcta : Código para una aplicación basada en SwiftUI con La visualización de errores se puede encontrar en Github en la carpeta CombineFetchAPI-Error .Si inicialmente planeó no manejar los errores, puede hacerlo sin Future <[Movie], MovieStoreAPIError> y devolver lo habitualAnyPublisher <[Película], Nunca> en el método fetchMoviesLight :
Código para una aplicación basada en SwiftUI con La visualización de errores se puede encontrar en Github en la carpeta CombineFetchAPI-Error .Si inicialmente planeó no manejar los errores, puede hacerlo sin Future <[Movie], MovieStoreAPIError> y devolver lo habitualAnyPublisher <[Película], Nunca> en el método fetchMoviesLight : La ausencia de errores ( Nunca ) nos permite usar una asignación de "suscriptor" muy simple (a: \ .movies, on: self) :
La ausencia de errores ( Nunca ) nos permite usar una asignación de "suscriptor" muy simple (a: \ .movies, on: self) : Todo funcionará como antes:
Todo funcionará como antes:
Conclusión
Usar el marco Combinar para procesar una secuencia de valores que aparecen asíncronamente en el tiempo es muy simple y fácil. Los operadores que ofrece Combine son potentes y flexibles. Combinar nos permite evitar escribir código asincrónico complejo utilizando la cadena ascendente de editores Editores , operadores y suscriptores integrados . Combine se construye en un nivel más bajo que Foundation , en muchos casos no necesita Foundation y tiene un rendimiento sorprendente. SwiftUI también está fuertemente vinculado a Combine<gracias a su @ObservableObject , @Binding y @EnvironmentObject .
SwiftUI también está fuertemente vinculado a Combine<gracias a su @ObservableObject , @Binding y @EnvironmentObject .iOSLos desarrolladores llevan mucho tiempo esperando Appleeste tipo de marco oficial, y finalmente este año sucedió.Enlaces:Obtención de la API con el asíncrono marco del Apple Remote Combinarel intento! Swift NYC 2019 - Comenzando con Combine"El último tutorial de framework Combine en Swift".Combine: Programación asincrónica con SwiftIntroducing Combine - WWDC 2019 - Videos - Apple Developer. session 722(sinopsis de la sesión 722 "Introducción a la combinación" en ruso)Combine in Practice - WWDC 2019 - Videos - Apple Developer. sesión 721(Sinopsis de la sesión 721 "Uso práctico de Combine" en ruso)SwiftUI & Combine: Together is better. Por qué SwiftUI y Combine te ayudan a crear mejores aplicaciones.MovieSwiftUI .Visualice Combine Magic con SwiftUI Parte 1 ()Visualice Combine Magic con SwiftUI - Parte 2 (Operadores, suscribirse y cancelar en Combine)Visualize Combine Magic with SwiftUI Part 3 (See Combine Merge and Append in Action)
Visualize Combine Magic with SwiftUI — Part 4Visualize Combine Magic with SwiftUI — Part 5Getting Started With the Combine Framework in SwiftTransforming Operators in Swift Combine Framework: Map vs FlatMap vs SwitchToLatestCombine's FutureUsing CombineURLSession and the Combine framework