Combine est un framework
Swift
réactif fonctionnel qui a été récemment implémenté pour toutes
Apple
plateformes
Apple
, y compris
Xcode 11
. Avec
Combine, il est très facile de traiter des séquences de valeurs qui apparaissent de manière asynchrone dans le temps. Il simplifie également le code asynchrone en abandonnant la délégation et les
rappels imbriqués complexes.
Mais étudier
Combiner lui-même au début peut ne pas sembler si simple. Le fait est que les principaux «acteurs» de
Combine sont des concepts abstraits tels que «éditeurs»
Éditeurs , «abonnés»
Abonnés et opérateurs
Opérateurs , sans lesquels il ne sera pas possible d'avancer dans la compréhension de la logique de fonctionnement de
Combine . Cependant, étant donné
Apple
fournit aux développeurs des "éditeurs", des "abonnés" et des opérateurs prêts à l'emploi, le code écrit avec
Combine est très compact et bien lisible.
Vous le verrez dans l'exemple d'une application liée à la
récupération asynchrone
des informations sur les
films de la très populaire base de données
TMDb . Nous allons créer deux applications différentes:
UIKit et
SwiftUI , et montrer comment
Combine fonctionne avec elles.

J'espère que cet article vous facilitera l'apprentissage du
Combine . Le code de toutes les applications développées dans cet article se trouve sur
Github .
La moissonneuse-batteuse comprend plusieurs composants principaux:
Publisher Publisher .

Éditeurs Les
éditeurs sont des TYPES qui apportent des
valeurs à tous ceux qui se soucient.
Le concept «éditeur» de l'éditeur est implémenté dans
Combine en tant que
protocole , et non en tant que TYPE spécifique. Le protocole
Publisher a associé des TYPES génériques pour la valeur de sortie et l'erreur d'
échec .
Un «éditeur» qui ne publie jamais d'erreur utilise l'erreur TYPE
Never for a
Failure .


Apple
fournit aux développeurs des implémentations spécifiques de «éditeurs» prêts à l'emploi:
Just ,
Future ,
Empty ,
Deferred ,
Sequence ,
@Published , etc. Des "éditeurs" sont également ajoutés aux classes
Foundation :
URLSession , <
NotificationCenter ,
Timer .
Abonné "Abonné".

C'est également un protocole
protocolaire qui fournit une interface pour «s'abonner» aux
valeurs d'un «éditeur». Il a associé des TYPES génériques pour les erreurs d'
entrée et d'
échec . Évidemment, les TYPES de Publisher
Publisher et
Subscriber Subscriber doivent correspondre.

Pour tout éditeur Publisher, il existe deux
abonnés intégrés:
regrouper et
attribuer :

Le récepteur «Subscriber»
est basé sur deux fermetures: une fermeture,
receiveValue , est exécutée lorsque vous obtenez les
valeurs , la deuxième fermeture,
receiveCompletion , est exécutée lorsque la «publication» est terminée (normalement ou avec une erreur).

L'
affectation «Abonné» avance chaque valeur reçue vers le
key Path
spécifié.
Abonnement "Abonnement".

Premièrement, l'éditeur «éditeur» crée et livre l'abonnement «Abonnement»
à l' abonné «
Abonné » via sa méthode de
réception (abonnement :) :

Après cela, l'
abonnement peut envoyer ses
valeurs aux «abonnés» des
abonnés en utilisant deux méthodes:

Si vous avez fini de travailler avec l'
abonnement Abonnement , vous pouvez appeler sa méthode
cancel () :

Sujet "Sujet".

Il s'agit d'un protocole de
protocole qui fournit une interface pour les deux clients, à la fois pour «l'éditeur» et pour «l'abonné». Essentiellement, le sujet «
subject » est le «publisher»
Publisher , qui peut accepter la valeur d'
entrée Input et que vous pouvez utiliser pour «injecter» les
valeurs dans le flux en appelant la méthode
send () . Cela peut être utile lors de l'adaptation du code impératif existant dans les modèles
combinés .
Opérateur Opérateur .

À l'aide de l'opérateur, vous pouvez créer un nouvel «éditeur» d'éditeur à partir d'un autre «éditeur» d'éditeur en convertissant, en filtrant et même en combinant les
valeurs des nombreux
éditeurs en upstream
précédents.

Vous voyez ici beaucoup de noms d'opérateurs familiers:
compactMap ,
map ,
filter ,
dropFirst ,
append .
Éditeurs de la Fondation Éditeurs intégrés à la Fondation .
Apple
fournit également aux développeurs plusieurs des fonctionnalités
Combine déjà intégrées dans le framework
Foundation <, c'est-à-dire les éditeurs des éditeurs, pour des tâches telles que la récupération de données à l'aide d'
URLSession , l'utilisation de notifications à l'aide de
Notification ,
Timer et les propriétés de surveillance basées sur
KVO . Cette compatibilité intégrée nous aidera vraiment à intégrer le framework
Combine dans notre projet actuel.
Pour en savoir plus à ce sujet, consultez l'article
«Le didacticiel ultime du framework Combine dans Swift» .
Qu'apprenons-nous à faire avec Combine ?
Dans cet article, nous allons apprendre à utiliser le framework
Combine pour récupérer des données de film à partir du site Web
TMDb . Voici ce que nous étudierons ensemble:
- Utiliser le futur "éditeur" pour créer une fermeture avec Promise pour une seule valeur: valeur ou erreur.
- Utilisation de l' URL " session " de l'éditeur " datataskPublisher " pour "s'abonner" aux données publiées par un
UR
L. donné - Utilisation de l'opérateur tryMap pour convertir des données de données à l' aide d'un autre éditeur Publisher.
- Utilisation de l'opérateur de décodage pour convertir les données de données en un objet décodable et les publier pour transmission aux éléments suivants de la chaîne.
- Utiliser l'opérateur récepteur pour «s'abonner» à un «éditeur» d'éditeur à l'aide de fermetures.
- Utilisez l'instruction assign pour vous «abonner» à l'éditeur «éditeur» et attribuer la valeur qu'il fournit au
key Path
donné.
Projet initial
Avant de commencer, nous devons nous enregistrer pour recevoir la clé
API
sur le site Web
TMDb . Vous devez également télécharger le projet initial à partir du référentiel
GitHub .
Assurez-vous de placer votre clé
API
dans la
classe MovieStore de classe dans
let apiKey constant.

Voici les principaux blocs de construction à partir desquels nous allons créer notre projet:
- 1. À l'intérieur du fichier
Movie.swift
trouvent des modèles que nous utiliserons dans notre projet. La structure racine de struct MoviesResponse implémente le protocole Decodable , et nous l'utiliserons lors du décodage JSON
données JSON
dans un modèle. La structure MoviesResponse a une propriété results , qui implémente également le protocole Decodable et est une collection de films [Movie] . C'est elle qui nous intéresse:

- 2. Énumération énumération MovieStoreAPIError implémente le protocole d' erreur . Notre
API
utilisera cette énumération pour représenter différents types d'erreurs: erreurs de récupération d' URL
urlError , erreurs de décodage decodingError et erreurs d'extraction de données responseError .

- 3. Notre
API
possède un protocole MovieService avec une seule méthode, fetchMovies (de endpoint: Endpoint) , qui sélectionne les films [Movie] en fonction du paramètre de endpoint . Le point de terminaison lui-même est un enum enum qui représente un point de terminaison pour accéder à la base de données TMDb pour récupérer des films comme nowPlaying (dernier), populaire (populaire), topRated (haut) et à venir (à venir bientôt à l'écran).

- 4. La classe MovieStore est une classe spécifique qui implémente le protocole MovieService pour récupérer des données à partir du site TMDb . Dans cette classe, nous implémentons la méthode fetchMovies (...) en utilisant Combine .

- 5. La classe MovieListViewController est la principale classe ViewController dans laquelle nous utilisons la méthode Sink pour «souscrire» à la méthode de lecture de film fetchMovies (...) , qui renvoie le futur «éditeur», puis mettre à jour la table TableView avec de nouvelles données de film films utilisant la nouvelle
API
DiffableDataSourceSnapshot .
Avant de commencer, examinons certains des composants de base de
Combine que nous utiliserons pour
API
récupération de données à distance.
«Abonnez-vous» à «l'éditeur» en utilisant évier et ses fermetures.
Le moyen le plus simple de «souscrire» à «l'éditeur» de l'éditeur est d'utiliser le
récepteur avec ses fermetures, dont l'une sera exécutée chaque fois que nous obtiendrons une nouvelle
valeur , et l'autre lorsque «l'éditeur» aura fini de livrer les
valeurs .

N'oubliez pas que dans
Combine, chaque «abonnement» renvoie une
annulation , qui sera supprimée dès que nous quitterons notre contexte. Afin de maintenir un «abonnement» pendant une période plus longue, par exemple, pour obtenir des valeurs de manière asynchrone, nous devons enregistrer «l'abonnement» dans la propriété
subscription1 . Cela nous a permis d'obtenir systématiquement toutes les
valeurs (7,8,3,4) .
Future «publie» de façon asynchrone une seule valeur: soit une valeur, soit une erreur d' échec .
Dans le cadre
Combine , le
futur «éditeur» peut être utilisé pour obtenir de manière asynchrone un seul TYPE de
résultat à l' aide d'une fermeture. La fermeture a un paramètre -
Promise , qui est une fonction de TYPE
(Result <Output, Failure>) -> Void .
Regardons un exemple simple pour comprendre comment fonctionne le
futur éditeur:

Nous créons
Future avec un résultat réussi de TYPE
Int et une erreur de TYPE
Never . Dans la fermeture
Future , nous utilisons
DispatchQueue.main.asyncAfter (...) pour retarder l'exécution du code de
2 secondes, simulant ainsi un comportement asynchrone. À l'intérieur de la fermeture, nous
renvoyons Promise avec un résultat de
promesse réussi (.success (...)) en tant que valeur entière aléatoire
Int comprise entre
0 et
100 . Ensuite, nous utilisons deux abonnements
futurs -
annulable et
annulable1 - et les deux donnent le même résultat, bien qu'un nombre aléatoire soit généré à l'intérieur.
1.
Il convient de noter que l '«éditeur» de Future in Combine présente certaines caractéristiques comportementales par rapport aux autres «éditeurs»:
- Le futur "éditeur" publie toujours "UNE valeur ( valeur ou erreur), et ceci termine son travail.
- Le futur «éditeur» est une classe (
reference type
), contrairement aux autres «éditeurs», qui sont principalement des structures struct ( value type
), et il est transmis en tant que paramètre une fermeture de promesse , qui est créée immédiatement lors de l'initialisation de l'instance d'éditeur futur . C'est-à-dire que la clôture de la promesse est transmise avant que tout abonné « abonné » ne s'abonne à la future instance «éditeur». L '«éditeur» de Future n'a absolument pas besoin d'un «abonné» pour son fonctionnement, comme tous les autres éditeurs ordinaires l'exigent. C'est pourquoi le texte «Bonjour de l'intérieur du futur!» N'est imprimé qu'une seule fois dans le code ci-dessus.
- Le futur "éditeur" est un "éditeur"
eager
(impatient), contrairement à la plupart des autres "éditeurs" paresseux ("publier" uniquement s'il y a un "abonnement"). Ce n'est qu'une fois que le futur éditeur a fermé sa promesse que le résultat est mémorisé et ensuite transmis aux «abonnés» actuels et futurs. D'après le code ci-dessus, nous voyons que lorsque vous vous «abonnez» à plusieurs reprises au futur éditeur, vous obtenez toujours la même valeur «aléatoire» (dans ce cas 6 , mais elle peut être différente, mais toujours la même), bien qu'elle soit utilisée dans la fermeture valeur int aléatoire.
Une telle logique de «l'éditeur» de
Future permet de l'utiliser avec succès pour stocker un résultat calculé asynchrone consommateur de ressources et de ne pas perturber le «serveur» pour les multiples «abonnements» ultérieurs.
Si une telle logique de l '«éditeur» de
Future ne vous convient pas et que vous voulez que votre
Future soit appelé
paresseux et chaque fois que vous obtenez de nouvelles valeurs
Int aléatoires, alors vous devriez «boucler»
Future dans
Deferred :

Nous utiliserons
Future de manière classique, comme suggéré dans
Combine , c'est-à-dire comme un «éditeur» asynchrone calculé «partagé».
Remarque 2. Nous devons faire une dernière remarque concernant l '«abonnement» utilisant le récepteur au «Publisher» asynchrone. La méthode Sink renvoie AnyCancellable , dont nous ne nous souvenons pas constamment. Cela signifie que Swift
détruira AnyCancellable au moment où vous quitterez le contexte donné, ce qui se produit sur le main thread
. Ainsi, il s'avère que AnyCancellable est détruit avant que la fermeture avec Promise ne puisse démarrer sur le main thread
. Lorsque AnyCancellable est détruit, sa méthode d' annulation est appelée, ce qui annule dans ce cas «l'abonnement». C'est pourquoi nous nous souvenons de nos «abonnements» de puits au futur dans les variables annulables <et annulables1 ou dans Set <AnyCancellable> () .
Utilisation de Combine pour récupérer des films sur le site Web de TMDb .
Pour commencer, vous ouvrez le projet de démarrage et accédez au fichier
MovieStore.swift
et à la méthode
fetchMovies avec une implémentation vide:

En utilisant la méthode
fetchMovies, nous pouvons sélectionner différents films en définissant des valeurs spécifiques pour le
paramètre d' entrée de
point final de TYPE
Endpoint . TYPE
Endpoint est une
énumération d' énumération qui prend les valeurs
nowPlaying (actuel), à
venir (bientôt à l'écran),
populaire (populaire),
topRated (haut):

Commençons par initialiser
Future avec une fermeture de
callback
. Reçu
Future, nous vous rembourserons ensuite.

À l'intérieur de la fermeture de
callback
, nous générons l'
URL
de la valeur correspondante du
paramètre d' entrée de
point final en utilisant la fonction
generateURL (avec endpoint: Endpoint) :

Si l'
URL
correcte n'a pas pu être générée, nous
renvoyons une erreur à l'aide de
promise (.failure (.urlError (...)) , sinon nous allons de l'avant et implémentons l'
URL «
session éditeur»
URLSession.dataTaskPublisher .
Pour "souscrire" aux données d'une certaine
URL
nous pouvons utiliser la méthode
datataskPublisher intégrée à la classe
URLSession , qui prend l'
URL
comme paramètre et renvoie l'éditeur "publisher" avec les données de sortie du tuple
(data: Data, response: URLResponse) et une erreur
URLError .

Pour convertir un éditeur Publisher vers un autre éditeur Publisher, utilisez l'opérateur
tryMap . Par rapport à la
carte , l'opérateur
tryMap peut
renvoyer une erreur
Error throws à l' intérieur d'une fermeture, ce qui nous renvoie le nouvel éditeur Publisher.
Dans l'étape suivante, nous utiliserons l'opérateur
tryMap pour vérifier le code
http
statusCode de la réponse de
réponse pour nous assurer que sa valeur est comprise entre
200 et
300 . Sinon, nous lançons
la valeur d'erreur d'
énumération MovieStoreAPIError enum responseError . Sinon (lorsqu'il n'y a pas d'erreur), nous renvoyons simplement les données reçues
au prochain
éditeur de la chaîne «éditeur».

Dans l'étape suivante, nous utiliserons l'opérateur de
décodage , qui décode les données
JSON
sortie du
tryMap «éditeur»
précédent dans
MovieResponse <Model à l'aide de
JSONDecoder .

...
jsonDecoder est configuré pour un format de date spécifique:

Pour que le traitement soit effectué sur le
main thread
, nous utiliserons l'opérateur
receive (on :) et passerons
RunLoop.main comme paramètre d'entrée. Cela permettra à "l'abonné" d'obtenir la valeur de
valeur sur le thread
main
.

Enfin, nous sommes arrivés à la fin de notre chaîne de transformation, et là, nous utilisons le
puits pour obtenir un abonnement «
abonnement » à la «chaîne» formée d'éditeurs «éditeurs». Pour initialiser une instance de la classe
Sink , nous avons besoin de deux choses, bien que l'une d'entre elles soit facultative:
- fermeture recevoirValeur :. Il sera appelé chaque fois qu'un abonnement "abonnement" reçoit une nouvelle valeur de l'éditeur "éditeur".
- Clôture de la réception: (Facultatif). Il sera appelé une fois que l' éditeur «éditeur» a fini de publier la valeur , il recevra l'énumération d' achèvement , que nous pouvons utiliser pour vérifier si la «publication» des valeurs est vraiment terminée ou si l'achèvement est dû à une erreur.
Dans la fermeture de
receiveValue , nous
invoquons simplement
une promesse avec une option
.success et une valeur de
0 $ .résultats , qui dans notre cas est un tableau de films
films . Dans la fermeture de
receiveCompletion, nous vérifions si l'
achèvement comporte une erreur d'
erreur , puis transmettons l'erreur de
promesse correspondante avec l'option
.failure .

Notez que nous collectons ici toutes les erreurs «rejetées» dans les étapes précédentes de la «chaîne des éditeurs».
Ensuite, nous mémorisons l'abonnement «souscription» dans la propriété
Set <AnyCancellable> () .
Le fait est que l'abonnement «abonnement» est
annulable , c'est un tel protocole qui détruit et efface tout après l'achèvement de la fonction
fetchMovies . Pour garantir la conservation de l'abonnement «abonnement» même après l'achèvement de cette fonction, il faut se souvenir de l'abonnement «
abonnement » dans la variable externe à la fonction
fetchMovies . Dans notre cas, nous utilisons la propriété
subscriptions , qui a le type
Set <AnyCancellable> () et utilisons la
méthode .store (in: & self.subscriptions) , qui garantit la
convivialité de l ' «abonnement» une fois que la fonction
fetchMovies a terminé son travail.

Ceci conclut la formation de la méthode
fetchMovies pour sélectionner des films dans la base de données
TMDb en utilisant le framework
Combine . La méthode
fetchMovies , en tant que paramètre d'entrée
de, prend la valeur de l'
énumération enum Endpoint , c'est-à-dire quels films spécifiques nous intéressent:
.nowPlaying - films actuellement à l'écran,
.upcoming - films à venir,
.popular - films populaires,
.topRated - meilleurs films, c'est-à-dire avec une cote très élevée.
Essayons d'appliquer cette
API
à la conception d'applications avec les interfaces utilisateur
UIKit habituelles sous la forme d'un
Table View Controller
:

et à une application dont l'interface utilisateur est construite en utilisant le nouveau
framework déclaratif
SwiftUI :

"Abonnez-vous" aux films des films View Controller
habituels.
Nous passons au fichier
MovieListViewController.swift et dans la méthode
viewDidLoad appelons la méthode
fetchMovies .

Dans notre méthode
fetchMovies, nous utilisons le
movieAPI développé précédemment et sa méthode
fetchMovies avec le paramètre
.nowPlaying comme
point de terminaison du paramètre d' entrée
from . Autrement dit, nous choisirons les films qui sont actuellement sur les écrans des salles de cinéma.

La
méthode movieAPI.fetchMovies (from: .nowPlaying) renvoie le
futur "éditeur", auquel nous "souscrivons" à l'aide de
sink , et lui fournissons deux fermetures. Dans la fermeture de
receiveCompletion ,
nous vérifions s'il y a une erreur d'
erreur et affichons un avertissement d'urgence à l'alerte de l'utilisateur avec le message d'erreur affiché.

Dans la fermeture
receiveValue, nous appelons la méthode
generateSnapshot et lui passons les
films sélectionnés.

La fonction
generateSnapshot génère un nouveau
NSDiffableDataSourceSnapshot à l' aide de nos
films et applique l'
instantané résultant au
diffableDataSource de notre table.
Nous lançons l'application et
observons comment
UIKit fonctionne avec les «éditeurs» et les «abonnés» du framework
Combine . Il s'agit d'une application très simple qui ne vous permet pas de syntoniser diverses collections de films - désormais présentés à l'écran, populaires, très appréciés ou qui vont apparaître à l'écran dans un avenir proche. Nous ne voyons que les films qui vont apparaître à l'écran (. À
venir ). Bien sûr, vous pouvez le faire en ajoutant un élément d'
UI
pour définir les valeurs de l'énumération de point
final , par exemple, à l'aide de
Stepper
ou de
Segmented Control
, puis mettez à jour l'interface utilisateur. C'est bien connu, mais nous ne le ferons pas dans une
application basée sur
UIKit , mais laissons cela au nouveau cadre déclaratif
SwiftUI .
Le code d'une
application basée sur
UIKit se trouve sur
Github dans le
CombineFetchAPICompleted-UIKit
.
Utilisez SwiftUI pour afficher des films films
.
CombineFetchAPI-MY
SwiftUI File
->
New
->
Project
Single View App
iOS
:

UI
—
SwiftUI :

Movie.swift
Model
,
TMDb MovieStore.swift
,
MovieStoreAPIError.swift
MovieService.swift
,
MovieService
Protocol
:
SwiftUI ,
Codable ,
JSON
,
Identifiable , si l' on veut faciliter l'affichage de la liste des films [Film] sous la forme d'une liste d' une liste . Les modèles dans SwiftUI n'ont pas besoin d'être équitables et hashable , comme requis par UIKit API
pour UITableViewDiffableDataSource dans l' application UIKit précédente . Par conséquent, nous supprimons de la structure < struct Movie toutes les méthodes associées aux protocoles Equatable et Hashable :
............................
Il existe un excellent article identifiable qui montre la différence et les similitudes entre les Swift
protocoles.Identifiable , hashable et équitable .L'interface utilisateur que nous créerons à l'aide de SwiftUI est basée sur le fait que nous changeons le point de terminaison lors de la récupération des données et obtenons la collection de films dont nous avons besoin, qui est présentée sous la forme d'une liste:
ainsi que dans le cas d' UIKit , les données sont échantillonnées à l'aide de la fonction movieAPI .fetchMovies (from endpoint: Endpoint) , qui obtient le point de terminaison souhaité et renvoie le "publisher" Future <[Movie, MovieStoreAPIError]> . Si nous jetons un œil à l'énumération Endpoint , nous verrons que nous pouvons initialiser l'option souhaitéecase Endpoint index :

,
movies ,
indexEndpoint Endpoint .
View Model
,
MoviesViewModel ,
ObservableObject .
MoviesViewModel.swift View Model
:
@Published :
@Published var indexEndpoint: Int — ,
@Published var movies: [Movie] - .
@Published indexEndpoint ,
indexEndpoint ,
$indexEndpoint .
MoviesViewModel «»
$indexEndpoint «»
AnyPublisher<[Movie], Never> ,
movieAPI.fetchMovies (from: Endpoint (index: indexPoint)) flatMap .
Ensuite, nous nous «abonnons» à cet «éditeur» nouvellement reçu en utilisant un assing «abonné» très simple (à: \ .movies, on: self) et attribuons la valeur reçue de «l'éditeur» au tableau de sortie des films . Nous pouvons appliquer l' assing «abonnement» (à: \ .movies, on: self) uniquement si «l'éditeur» ne renvoie pas d'erreur, c'est-à-dire qu'il a un TYPE d'erreur Never . Comment y parvenir? Utilisation de l' opérateur replaceError (avec: []) , qui remplace toute erreur par un tableau de films vide de films .Autrement dit, la première version plus simple de notre application SwiftUI n'affichera pas d'informations sur les erreurs possibles pour l'utilisateur.,
View Model
,
UI
.
ContentView.swift View Model
@EnvironmentObject var moviesViewModel Text(«Hello, World!»)Text("\(moviesViewModel.indexEndpoint)") ,
indexEndpoint .

View Model
indexEndpoint = 2 , , (
Upcoming ):

UI
Éléments pour contrôler la collection de films que nous voulons montrer. Voici Stepper :
... et Picker : les
deux utilisent le "publisher" $ moviesViewModel.indexEndpoint de la nôtre View Model
, et avec l'un d'eux (de toute façon, lequel) nous pouvons sélectionner la collection de films dont nous avons besoin:
Ensuite, nous ajoutons la liste des films reçus en utilisant List et ForEach et des attributs minimaux film lui-même film :
Liste des films affichés filmsViewModel.movies que nous prenons également du nôtre View Model
:
Nous N'utilisons PAS le $ publisher $ moviesViewModel.movies avec le signe $, car nous n'allons rien éditer dans cette liste de films. Nous utilisons la propriété habituelle moviesViewModel.movies .Vous pouvez rendre la liste de films plus intéressante en affichant dans chaque ligne de la liste l'affiche du film correspondant, URL
qui est présentée dans la structure du film :
Nous empruntons cette opportunité à Thomas Ricouard de son beau projet MovieSwiftUI .Comme dans le cas du chargement de films , pour l'image UIImage , nous avons le service ImageService , qui implémente la méthode fetchImage avec CombineDe retour « éditeur» AnyPublisher <?, un UIImage Never> :
... et ImageLoader de classe finale: ObservableObject , met en œuvre le protocole ObservableObject avec @Published propriété d' image: UIImage? :
La seule condition requise par le protocole ObservableObject est l'existence de la propriété objectWillChange . SwiftUI utilise cette propriété pour comprendre que quelque chose a changé dans l'instance de cette classe et dès que cela se produit, met à jour toutes les vues qui dépendent de l'instance de cette classe. En règle générale, le compilateur crée automatiquement une propriété objectWillChange et toutes les propriétés @Published la notifient également automatiquement. Dans le cas de certaines situations exotiques, vous pouvez créer manuellement un objectWillChange et le notifier des modifications. Nous avons juste un tel cas.Dans la classe ImageLoader @Published var image:UIImage? . ,
ImageLoader , «»
$image «»
loadImage() ,
poster size @Published var image:UIImage? .
Nous informerons objectWillChange de ces modifications .Nous pouvons avoir beaucoup de ces images dans le tableau, ce qui entraîne des coûts de temps importants, nous utilisons donc la mise en cache des instances imageLoader de la classe ImageLoader :
Nous avons une vue spéciale pour lire l' affiche du film MoviePosterImage :
... et nous l'utiliserons lors de l'affichage d'une liste de films dans notre ContentView principal :
Le code de l' application basée sur SwiftUI sans affichage d'erreur peut être trouvé sur Github dans le dossier CombineFetchAPI-NOError
.Afficher les erreurs de récupération de film asynchrone à distance.
Jusqu'à présent, nous n'avons pas utilisé ou affiché d'erreurs qui se produisent lors de la sélection asynchrone à distance de films sur le site Web de TMDb . Bien que la fonction movieAPI.fetchMovies (de endpoint: Endpoint) que nous utilisons nous permette de le faire, car elle renvoie le "publisher" Future <[Movie, MovieStoreAPIError]> .Afin de permettre des erreurs, ajouter à notre View Model
autre @Published propriété moviesError: MovieStoreAPIError? cela représente l'erreur. Il s'agit d'une propriété facultative , sa valeur initiale est nil , ce qui correspond à l'absence d'erreur:
pour obtenir cette erreur moviesError ,
MoviesViewModel «»
sink :
moviesError UI
,
nil …
AlertView :

,
API
:
SwiftUI Github CombineFetchAPI-Error .
,
Future<[Movie],MovieStoreAPIError> ,
AnyPublisher <[Movie], Never> dans la méthode fetchMoviesLight :
L'absence d'erreurs ( Never ) nous permet d'utiliser une assignation "abonné" très simple (à: \ .movies, on: self) :
Tout fonctionnera comme avant:
Conclusion
L'utilisation du framework Combine pour traiter une séquence de valeurs apparaissant de manière asynchrone dans le temps est très simple et facile. Les opérateurs proposés par Combine sont puissants et flexibles. Combine nous permet d'éviter d'écrire du code asynchrone complexe en utilisant la chaîne en amont des éditeurs Éditeurs , en utilisant des opérateurs et des abonnés intégrés . La moissonneuse-batteuse est construite à un niveau inférieur à Foundation , dans de nombreux cas n'a pas besoin de Foundation et a des performances incroyables.
SwiftUI est également fortement lié à Combine<grâce à ses @ObservableObject , @Binding et @EnvironmentObject .iOS
les développeurs attendent depuis longtemps Apple
ce genre de cadre officiel, et finalement cette année, c'est arrivé.Références:
Récupérer l'API Remote Async avec Apple Combine Frameworkessayez! Swift NYC 2019 - Premiers pas avec Combine"Le tutoriel ultime du framework Combine dans Swift".Combine: Programmation asynchrone avec SwiftPrésentation de Combine - WWDC 2019 - Vidéos - Développeur Apple. session 722(synopsis de la session 722 "Introduction to Combine" en russe)Combine in Practice - WWDC 2019 - Vidéos - Apple Developer. session 721(synopsis de la session 721 "Utilisation pratique de Combine" en russe)SwiftUI & Combine: Together is better. Pourquoi SwiftUI et Combine vous aident à créer de meilleures applications.MovieSwiftUI .Visualisez Combine Magic avec SwiftUI Partie 1 ()Visualize Combine Magic with SwiftUI – Part 2 (Operators, subscribing, and canceling in 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