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
UR
L. - 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 Path
dada.
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.swift
hay modelos que utilizaremos en nuestro proyecto. La estructura raíz de struct MoviesResponse implementa el protocolo Decodable , y lo usaremos cuando decodifiquemos datos JSON
en 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
API
utilizará esta enumeración para representar varios tipos de errores: errores de recuperación de URL
urlError , errores de decodificación de decodificación de error y errores de recuperación de datos de respuestaError .

- 3. Nuestra
API
tiene 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
API
DiffableDataSourceSnapshot .
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-MY
con la interfaz SwiftUI usando el menú File
-> New
-> Project
y seleccionamos la plantilla Single View App
en la sección iOS
:
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 Movie.swift
y colóquelo en la carpeta Model
necesaria para la interacción con TMDB archivos MovieStore.swift
, MovieStoreAPIError.swift
y MovieService.swift
, en consecuencia y colocarlos en carpetas MovieService
y Protocol
:
en SwiftUI requería que
era codificable , si nos vamos a llenar sus JSON
datos 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 Swift
protocolos.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 :
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 .
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 Model
para nuestras películas, comencemos a crear UI
. Añadir al archivo ContentView.swift nuestra View Model
forma @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 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 agregueUI
Elementos para controlar qué colección de películas queremos mostrar. Este es Stepper :
... y Picker :
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 :
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, URL
que 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> :
... 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? . ,
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 :
... 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 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 Model
otra @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
error moviesError se puede mostrar UI
si no es nulo ...
usando AlertView : Simulamos
este error simplemente quitando la API
clave 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 :
La ausencia de errores ( Nunca ) nos permite usar una asignación de "suscriptor" muy simple (a: \ .movies, on: self) :
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 .iOS
Los desarrolladores llevan mucho tiempo esperando Apple
este 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