Combine ist ein funktionales reaktives
Swift
Framework, das kürzlich für alle
Apple
Plattformen, einschließlich
Xcode 11
implementiert wurde. Mit
Combine ist es sehr einfach, Wertesequenzen zu verarbeiten, die im Laufe der Zeit asynchron erscheinen. Es vereinfacht auch den asynchronen Code, indem die Delegierung und komplexe verschachtelte
Rückrufe aufgegeben werden.
Aber das Lernen von
Combine selbst scheint zunächst nicht so einfach zu sein. Tatsache ist, dass die Hauptakteure von
Combine so abstrakte Konzepte wie "Verlage", "Abonnenten",
Abonnenten und
Operatoren sind , ohne die es nicht möglich sein wird, die Logik der Funktionsweise von
Combine zu verstehen. Aufgrund der Tatsache, dass
Apple
Entwicklern fertige "Publisher", "Abonnenten" und Betreiber zur Verfügung stellt, ist der mit
Combine geschriebene Code jedoch sehr kompakt und gut lesbar.
Sie sehen dies am Beispiel einer Anwendung, die sich auf das asynchrone
Abrufen von Filminformationen aus der sehr beliebten
TMDb- Datenbank
bezieht . Wir werden zwei verschiedene Anwendungen erstellen:
UIKit und
SwiftUI und zeigen, wie
Combine mit ihnen zusammenarbeitet.

Ich hoffe, dieser Artikel erleichtert Ihnen das Erlernen von
Combine . Den Code für alle in diesem Artikel entwickelten Anwendungen finden Sie auf
Github .
Mähdrescher hat mehrere Hauptkomponenten:
Verlag Verlag .

Verlage
Verlage sind TYPEN,
die jedem, der sich interessiert,
Werte liefern.
Das Publisher- Publisher-Konzept wird in
Combine als
Protokoll und nicht als spezifischer TYPE implementiert. Dem
Publisher- Protokoll sind generische TYPEN für Ausgabewerte und Fehler zugeordnet.
Ein "Publisher", der niemals einen Fehler veröffentlicht, verwendet den TYPE
Never for a
Failure- Fehler.


Apple
bietet Entwicklern spezifische Implementierungen von vorgefertigten „Publishern“:
Just ,
Future ,
Empty ,
Deferred ,
Sequence ,
@Published usw. Den
Foundation- Klassen werden auch "Publisher" hinzugefügt:
URLSession , <
NotificationCenter ,
Timer .
"Abonnent" Abonnent .

Es ist auch ein Protokollprotokoll, das eine Schnittstelle zum „Abonnieren“ von
Werten eines „Herausgebers“ bietet. Es sind generische TYPEN für
Eingabe- und Fehlerfehler zugeordnet. Offensichtlich müssen die TYPEN von Publisher
Publisher und
Subscriber Subscriber übereinstimmen.

Für jeden Publisher-Publisher gibt es zwei integrierte
Subskribenten :
sink und
assign :

Die
Senke "Subscriber" basiert auf zwei Abschlüssen: Ein Abschluss, "
receiveValue" , wird ausgeführt, wenn Sie die
Werte abrufen , der zweite Abschluss, "
receiveCompletion" , wird ausgeführt, wenn die "Veröffentlichung" abgeschlossen ist (normalerweise oder mit einem Fehler).

Die
Zuweisung "Abonnent" rückt jeden empfangenen Wert in Richtung des angegebenen
key Path
.
"Abonnement" Abonnement .

Zunächst erstellt der Verleger das Abonnement "Abonnement" und
übermittelt es an den Abonnenten "
Abonnenten " über die
Empfangsmethode (Abonnement :) :

Danach kann
Subscription seine
Werte auf zwei Arten
an die "Subscribers" von
Subscribers senden:

Wenn Sie mit dem
Abonnement fertig sind, können Sie die
cancel () -Methode aufrufen:

"Betreff" Betreff .

Dies ist ein Protokollprotokoll, das eine Schnittstelle für beide Clients bereitstellt, sowohl für den "Herausgeber" als auch für den "Abonnenten". Im Wesentlichen ist der
Betreff der Verleger, der den Eingabewert
Input annehmen kann und mit dem Sie die
Werte durch Aufrufen der
send () -Methode in den Stream „einfügen“
können . Dies kann hilfreich sein, wenn Sie vorhandenen Imperativ-Code in
Combine Models anpassen.
Betreiber Betreiber .

Mit dem Operator können Sie einen neuen Publisher "Publisher" aus einem anderen
Publisher "Publisher" erstellen, indem Sie die
Werte der vielen vorherigen
upstream
Publisher konvertieren, filtern und sogar kombinieren.

Sie sehen hier viele bekannte Bedienernamen:
compactMap ,
map ,
filter ,
dropFirst ,
append .
Herausgeber der Stiftung In die Stiftung eingebaute Herausgeber .
Apple
bietet Entwicklern auch einige der bereits integrierten
Kombinationsfunktionen im
Foundation <Framework,
dh Publishers 'Publishers, für Aufgaben wie das Abrufen von Daten mit
URLSession , das Arbeiten mit Benachrichtigungen mit
Notification ,
Timer und das Überwachen von Eigenschaften basierend auf
KVO . Diese eingebaute Kompatibilität wird uns wirklich helfen, das
Combine- Framework in unser aktuelles Projekt zu integrieren.
Weitere Informationen finden Sie im Artikel
„Das ultimative Kombinations-Framework-Tutorial in Swift“ .
Was lernen wir mit Combine zu tun?
In diesem Artikel erfahren Sie, wie Sie mit dem
Combine- Framework Filmdaten von der
TMDb- Website
abrufen . Folgendes werden wir gemeinsam lernen:
- Verwenden der "Publisher" -Zukunft , um mit Promise einen Abschluss für einen einzelnen Wert zu erstellen: entweder Wert oder Fehler.
- Verwenden des URLSession.datataskPublisher "Publisher", um Daten zu "abonnieren", die von einem bestimmten
UR
L veröffentlicht wurden. - Verwenden des tryMap- Operators zum Konvertieren von Daten Daten mit einem anderen Publisher-Publisher.
- Verwenden des Dekodierungsoperators zum Konvertieren von Daten in ein dekodierbares Objekt und zum Veröffentlichen für die Übertragung an nachfolgende Elemente der Kette.
- Verwenden des Senkenoperators zum Abonnieren eines Publishers mithilfe von Closures.
- Verwenden Sie die Anweisung assign, um den Publisher "publisher" zu abonnieren und den Wert, den er liefert, dem angegebenen
key Path
zuzuweisen.
Erstes Projekt
Bevor wir beginnen, müssen wir uns registrieren, um den
API
Schlüssel auf der
TMDb- Website zu erhalten. Sie müssen auch das ursprüngliche Projekt aus dem
GitHub- Repository herunterladen.
Stellen Sie sicher, dass Sie Ihren
API
Schlüssel in der
Klasse MovieStore in der
Klasse let apiKey constant platzieren.

Hier sind die Hauptbausteine, aus denen wir unser Projekt erstellen:
- 1. In der Datei
Movie.swift
befinden sich Modelle, die wir in unserem Projekt verwenden werden. Die Root-Struktur von struct MoviesResponse implementiert das Decodable- Protokoll und wird beim Decodieren von JSON
Daten in ein Modell verwendet. Die MoviesResponse- Struktur verfügt über eine results -Eigenschaft , die auch das Decodable- Protokoll implementiert und eine Sammlung von Filmen [Movie] ist . Sie interessiert uns:

- 2. Aufzählung MovieStoreAPIError implementiert das Fehlerprotokoll . Unsere
API
verwendet diese Enumeration, um verschiedene Arten von Fehlern darzustellen : URL
Abruffehler , urlError- , decodingError- Decodierungsfehler und responseError- Datenabruffehler.

- 3. Unsere
API
verfügt über ein MovieService- Protokoll mit einer einzigen Methode, fetchMovies (from endpoint: Endpoint) , mit der [Movie] -Filme basierend auf dem Endpoint- Parameter ausgewählt werden. Endpoint selbst ist eine Aufzählung von Aufzählungen, die einen Endpunkt für den Zugriff auf die TMDb- Datenbank darstellt, um Filme wie nowPlaying (neueste), popular (beliebte), topRated (top) und coming (bald auf dem Bildschirm) abzurufen .

- 4. Die MovieStore- Klasse ist eine bestimmte Klasse, die das MovieService- Protokoll zum Abrufen von Daten von der TMDb- Site implementiert . In dieser Klasse implementieren wir die Methode fetchMovies (...) mit Combine .

- 5. Die MovieListViewController- Klasse ist die ViewController-Hauptklasse , in der wir die Methode sink verwenden, um die Movie-Fetch-Methode fetchMovies (...) zu abonnieren, die den zukünftigen Publisher zurückgibt, und dann die TableView- Tabelle mit neuen Filmdaten zu aktualisieren Filme mit der neuen DiffableDataSourceSnapshot-
API
.
Bevor wir beginnen, schauen wir uns einige der grundlegenden
Combine- Komponenten an, die wir für
API
Remote Data Retrieval-
API
.
"Abonnieren" Sie den "Verlag" mit sink und seinen Verschlüssen.
Der einfachste Weg, den Publisher zu abonnieren, besteht darin,
sink mit seinen Closures zu verwenden, von denen einer ausgeführt wird, wenn wir einen neuen
Wert erhalten , und der andere, wenn der Publisher die Lieferung der
Werte beendet .

Denken Sie daran, dass in
Combine jedes "Abonnement" eine
Kündigungsmöglichkeit zurückgibt, die gelöscht wird, sobald wir unseren Kontext verlassen. Um ein "Abonnement" für eine längere Zeit beizubehalten, um beispielsweise asynchron Werte abzurufen, müssen Sie das "Abonnement" in der Eigenschaft "Abonnement1" speichern. Dadurch konnten wir alle
Werte (7,8,3,4) konsistent erhalten.
Future "veröffentlicht" asynchron einen einzelnen Wert: entweder einen Wert oder einen Fehler.
Im
Kombinationsframework kann der
Future "Publisher" verwendet werden, um mithilfe eines Abschlusses asynchron einen einzelnen
Ergebnistyp abzurufen. Der Abschluss hat einen Parameter -
Promise , eine Funktion von TYPE
(Result <Output, Failure>) -> Void .
Schauen wir uns ein einfaches Beispiel an, um zu verstehen, wie der
Future Publisher funktioniert:

Wir erstellen
Future mit einem erfolgreichen Ergebnis von TYPE
Int und einem Fehler von TYPE
Never . Innerhalb des
Future- Abschlusses verwenden wir
DispatchQueue.main.asyncAfter (...) , um die Ausführung des Codes um
2 Sekunden zu verzögern und damit ein asynchrones Verhalten zu simulieren. Innerhalb des Abschlusses geben wir
Promise mit einem erfolgreichen Ergebnis von
Promise (.success (...)) als zufälligen Integer-
Int- Wert im Bereich zwischen
0 und
100 zurück . Als Nächstes verwenden wir zwei
zukünftige Abonnements -
stornierbar und
stornierbar1 - und beide liefern dasselbe Ergebnis, obwohl eine Zufallszahl im Inneren generiert wird.
1.
Es ist zu beachten, dass der "Publisher" von Future in Combine im Vergleich zu anderen "Publishern" einige Verhaltensmerkmale aufweist:
- Der "Herausgeber" Future "veröffentlicht" immer "EINEN Wert ( Wert oder Fehler), und dies vervollständigt seine Arbeit.
- Future "Publisher" ist im Gegensatz zu anderen "Publishern", bei denen es sich hauptsächlich um Struktur- ( Werttyp- ) Strukturen handelt, eine Klasse (
reference type
). Sie wird als Parameter an den Promise- Abschluss übergeben, der unmittelbar nach der Initialisierung der Future- Publisher-Instanz erstellt wird. Das heißt, der Promise- Abschluss wird übertragen, bevor ein Abonnent " Abonnent " die zukünftige "Herausgeber" -Instanz überhaupt abonniert . Der "Publisher" von Future benötigt für seine Funktionsweise überhaupt keinen "Subscriber", wie es von allen anderen gewöhnlichen "Publishers" von Publishers verlangt wird. Aus diesem Grund wird der Text „Hallo aus der Zukunft!“ Im obigen Code nur einmal gedruckt.
- Zukünftiger "Publisher" ist ein
eager
(ungeduldiger) "Publisher", im Gegensatz zu den meisten anderen faulen "Publishern" ("Publizieren" nur, wenn es ein "Abonnement" gibt). Erst wenn der Future- Publisher sein Versprechen schließt, wird das Ergebnis gespeichert und an die aktuellen und zukünftigen „Abonnenten“ übermittelt. Aus dem obigen Code geht hervor, dass Sie beim wiederholten Abonnieren des zukünftigen Publishers immer den gleichen zufälligen Wert erhalten (in diesem Fall 6 , aber er kann unterschiedlich, aber immer gleich sein), obwohl er im Closure verwendet wird zufälliger int Wert.
Diese Logik des "Publishers" von
Future ermöglicht die erfolgreiche Speicherung eines asynchronen, ressourcenintensiven Berechnungsergebnisses und verhindert, dass der "Server" für nachfolgende mehrfache "Abonnements" gestört wird.
Wenn eine solche Logik des "Herausgebers" von
Future nicht zu Ihnen passt und Sie möchten, dass Ihr
Future faul ist und jedes Mal, wenn Sie neue zufällige
Int- Werte erhalten, sollten Sie
Future in
Deferred "umschließen":

Wir werden
Future auf eine klassische Art und Weise verwenden, wie in
Combine vorgeschlagen,
dh als "gemeinsam genutzten" berechneten asynchronen "Publisher".
Hinweis 2. Wir müssen noch eine Bemerkung zum "Abonnement" machen, indem wir die Senke für den asynchronen "Publisher" verwenden. Die sink- Methode gibt AnyCancellable zurück , an das wir uns nicht ständig erinnern. Dies bedeutet, dass Swift
AnyCancellable zerstört, wenn Sie den angegebenen Kontext verlassen. Dies geschieht im main thread
. Somit stellt sich heraus, dass AnyCancellable zerstört wird, bevor der Abschluss mit Promise im main thread
. Wenn AnyCancellable zerstört wird, wird seine Kündigungsmethode aufgerufen, die in diesem Fall das Abonnement kündigt . Aus diesem Grund erinnern wir uns an unsere zukünftigen Abos in den Variablen cancelable <und cancelable1 oder in Set <AnyCancellable> () .
Verwenden von Combine zum Abrufen von Filmen von der TMDb- Website.
Zunächst öffnen Sie das
MovieStore.swift
und
wechseln mit einer leeren Implementierung zur Datei
MovieStore.swift
und zur Methode
fetchMovies :

Mit der
fetchMovies- Methode
können wir verschiedene Filme auswählen, indem wir bestimmte Werte für den
Endpunkt- Eingabeparameter von TYPE
Endpoint festlegen. TYPE
Endpoint ist eine Aufzählung, die die Werte
nowPlaying (aktuell), coming (erscheint bald auf dem Bildschirm),
popular (beliebt),
topRated (oben)
annimmt :

Beginnen wir mit der Initialisierung von
Future mit einem
callback
. Erhaltene
Zukünftige werden wir dann erstatten.

Innerhalb des
callback
generieren wir die
URL
für den entsprechenden Wert des
Endpunkt- Eingabeparameters mit der Funktion
generateURL (with endpoint: Endpoint) :

Wenn die richtige
URL
nicht generiert werden konnte, geben wir einen Fehler mit
Promise (.failure (.urlError (...)) zurück , andernfalls implementieren wir die
URLSession.dataTaskPublisher des Herausgebers.
Um Daten von einer bestimmten
URL
zu "abonnieren"
URL
wir die in die
URLSession- Klasse integrierte
datataskPublisher- Methode verwenden, die die
URL
als Parameter verwendet und den Publisher "publisher" mit den Ausgabedaten des Tupels
(data: Data, response: URLResponse) und einem
URLError- Fehler
zurückgibt .

Verwenden Sie den
tryMap- Operator, um einen Publisher-Publisher in einen anderen Publisher-Publisher zu
konvertieren . Im Vergleich zu einer
Karte kann der
tryMap- Operator einen
Throws- Error- Fehler in einem Closure
auslösen , der uns den neuen Publisher-Publisher zurückgibt.
Im nächsten Schritt überprüfen wir mit dem
tryMap- Operator den
statusCode- http
Code der
Antwortantwort , um sicherzustellen, dass der Wert zwischen
200 und
300 liegt . Wenn nicht, dann werfen wir
den Fehlerwert responseError enum MovieStoreAPIError enumeration. Andernfalls (wenn keine Fehler vorliegen) geben wir die empfangenen Daten einfach
an den nächsten
Publisher in der Kette "Publisher" zurück.

Im nächsten Schritt werden wir den
Dekodierungsoperator verwenden , der die ausgegebenen
JSON
Daten des vorherigen Herausgebers
tryMap mithilfe von
JSONDecoder in
MovieResponse <Model
dekodiert .

...
jsonDecoder ist für ein bestimmtes Datumsformat konfiguriert:

Für die Verarbeitung des
main thread
wir den Operator
receive (on :) und übergeben
RunLoop.main als Eingabeparameter. Auf diese Weise kann der "Abonnent" den Wert im Hauptthread abrufen.

Schließlich haben wir das Ende unserer Transformationskette erreicht und nutzen
Senke , um ein
Abonnement für die gebildete "Kette" von Publishers "Publishers" zu erhalten. Um eine Instanz der
Sink- Klasse zu initialisieren, benötigen wir zwei Dinge, von denen eines optional ist:
- Abschluss ReceiveValue :. Es wird aufgerufen, wenn ein Abonnement einen neuen Wert vom Publisher erhält.
- ReceiveCompletion- Abschluss : (Optional). Es wird aufgerufen, nachdem der Publisher "Publisher" den Wert veröffentlicht hat, und erhält die Vervollständigungsaufzählung , mit der überprüft werden kann, ob die "Veröffentlichung" der Werte wirklich abgeschlossen ist oder die Vervollständigung auf einen Fehler zurückzuführen ist.
Innerhalb des
ReceiveValue- Abschlusses
rufen wir einfach
ein Versprechen mit einer
.success- Option und einem Wert von
0 US-Dollar auf. Results , in unserem Fall eine Reihe von
Filmen .
Während des
Empfangsabschlusses prüfen wir, ob der
Abschluss einen Fehlerfehler aufweist, und übergeben dann den entsprechenden
Versprechungsfehler mit der Option
.failure .

Beachten Sie, dass wir hier alle Fehler sammeln, die in den vorherigen Phasen der Publisher-Kette "rausgeworfen" wurden.
Als Nächstes merken wir uns das Abonnement „Abonnement“ in der Eigenschaft
Set <AnyCancellable> () .
Tatsache ist, dass das Abonnement "Abonnement"
kündbar ist. Es ist ein solches Protokoll, das nach Abschluss der Funktion "
fetchMovies" alles zerstört und löscht. Um die Beibehaltung des Abonnements "Abonnement" auch nach Abschluss dieser Funktion zu gewährleisten, müssen Sie das Abonnement "
Abonnement " in der Variablen außerhalb der Funktion "
fetchMovies "
speichern . In unserem Fall verwenden wir die Eigenschaft
subscriptions , die den
Typ Set <AnyCancellable> () hat, und die
Methode .store (in: & self.subscriptions) , mit der die
Verwendbarkeit des Abonnements sichergestellt wird, nachdem die Funktion
fetchMovies ihre Arbeit beendet hat.

Damit ist die Bildung der
fetchMovies- Methode
zum Auswählen von Filmen aus der
TMDb- Datenbank unter Verwendung des
Combine- Frameworks abgeschlossen. Die Methode
fetchMovies nimmt als Eingabeparameter
von den Wert der
Enum-Endpunkt- Enumeration an, d.
H. , Welche spezifischen Filme für uns von Interesse sind:
.nowPlaying - Filme, die gerade auf dem Bildschirm sind,
.upcoming - Filme, die bald kommen,
.popular - beliebte Filme,
.topRated - Top-Filme mit einer sehr hohen Bewertung.
Versuchen wir, diese
API
auf das Anwendungsdesign mit den üblichen
UIKit- Benutzeroberflächen in Form eines
Table View Controller
anzuwenden :

und für eine Anwendung, deren Benutzeroberfläche mit dem neuen deklarativen
SwiftUI- Framework erstellt wurde:

"Abonnieren" Sie Filme aus den üblichen View Controller
Filmen .
Wir wechseln in die Datei
MovieListViewController.swift und rufen in der Methode
viewDidLoad die Methode
fetchMovies auf .

In unserer Methode
fetchMovies verwenden wir die
zuvor entwickelte
movieAPI und die Methode
fetchMovies mit dem Parameter
.nowPlaying als
Endpunkt des Eingabeparameters
from . Das heißt, wir werden die Filme auswählen, die derzeit auf den Bildschirmen von Kinos laufen.

Die
movieAPI.fetchMovies (from: .nowPlaying) -Methode gibt
Future "publisher" zurück, den wir mit
sink "abonnieren" und mit zwei Closures versehen. Beim Abschluss von
receiveCompletion prüfen
wir , ob ein Fehler
vorliegt, und zeigen dem Benutzer eine Notfallwarnung mit der angezeigten Fehlermeldung an.

Beim
ReceiveValue- Abschluss rufen wir die
generateSnapshot- Methode auf und übergeben die ausgewählten
Filme an sie .

Die Funktion
generateSnapshot generiert aus unseren
Filmen einen neuen
NSDiffableDataSourceSnapshot und wendet den resultierenden
Snapshot auf
diffableDataSource unserer Tabelle an.
Wir starten die Anwendung und beobachten, wie
UIKit mit den "Herausgebern" und "Abonnenten" aus dem
Combine- Framework zusammenarbeitet. Dies ist eine sehr einfache Anwendung, mit der Sie nicht auf verschiedene Filmsammlungen zugreifen können - die jetzt auf dem Bildschirm angezeigt werden, populär sind, eine hohe Bewertung haben oder in naher Zukunft auf dem Bildschirm erscheinen werden. Wir sehen nur die Filme, die auf dem Bildschirm erscheinen werden (
.upcoming ). Dazu können Sie natürlich ein beliebiges
UI
hinzufügen, um die Werte der
Endpoint- Enumeration
festzulegen , z. B. mithilfe von
Stepper
oder
Segmented Control
, und anschließend die Benutzeroberfläche aktualisieren. Dies ist allgemein bekannt, aber wir werden dies nicht in einer
UIKit- basierten
Anwendung tun, sondern dem neuen deklarativen
SwiftUI- Framework
überlassen .
Der Code für eine
UIKit- basierte
Anwendung befindet sich auf
Github im
CombineFetchAPICompleted-UIKit
.
Verwenden Sie SwiftUI , um Filme Filme anzuzeigen
.
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
,
Erkennbar , wenn wir es uns leichter machen wollen, die Filmliste [Movie] als Liste List anzuzeigen . Modelle in SwiftUI müssen nicht gleichwertig und hashbar sein , wie dies von UIKit API
für die UITableViewDiffableDataSource in der vorherigen UIKit- Anwendung gefordert wurde . Deshalb entfernt sie von der Struktur < a struct Film all zugehörigen Methoden mit den Protokollen zu und hashable :
............................
Es gibt einen großartigen identifizierbaren Artikel , der den Unterschied und die Ähnlichkeiten zwischen Swift
Protokollen zeigt.Identifizierbar , Hashbar und Gleichwertig .Die Benutzeroberfläche, die wir mit SwiftUI erstellen, basiert auf der Tatsache, dass wir den Endpunkt beim Abrufen von Daten ändern und die benötigte Filmsammlung abrufen , die in Form einer Liste dargestellt wird:
Wie im Fall von UIKit werden die Daten mit der movieAPI- Funktion abgetastet .fetchMovies (vom Endpunkt: Endpunkt) , der den gewünschten Endpunkt abruft und den "Publisher" Future <[Movie, MovieStoreAPIError]> zurückgibt . Wenn wir uns die Endpoint- Aufzählung ansehen , werden wir sehen, dass wir die gewünschte Option initialisieren könnenFall der Anzeige Endpoint und dementsprechend die gewünschte Filmsammlung über den Index Index :
Folglich die gewünschten uns Filmsammlung zu erhalten Filme , genug , um den entsprechenden Index zu setzen indexEndpoint Transfer Endpoint . Machen wir es in View Model
der Klasse MoviesViewModel , die das ObservableObject- Protokoll implementiert . Fügen Sie eine neue Datei MoviesViewModel.swift für unsere in unserem Projekt hinzu View Model
:
In dieser sehr einfachen Klasse haben wir zwei @Published- Eigenschaften: eine @Published var indexEndpoint: Int- Eingabe, andere @Veröffentlichte Filme: [Film] - Ausgabe. Sobald wir setzen @Published vor Eigentum indexEndpoint , können wir beginnen , es als eine einfache Eigenschaft zu verwenden indexEndpoint , und als Verleger $ indexEndpoint .Beim Initialisieren einer Instanz unserer MoviesViewModel- Klasse müssen wir die Kette von der Eingabe "publisher" $ indexEndpoint bis zur Ausgabe "publisher" TYPE AnyPublisher <[Movie], Never> verlängern , die wir mithilfe der bereits bekannten Funktion movieAPI.fetchMovies (from: Endpoint (index ) erhalten : indexPoint)) und der flatMap- Operator .
Dann wir „subscribe“ in diesen neu erstellten „Verlag“ mit einem sehr einfachen „Teilnehmern“ assing (an: \ .movies, auf: Selbst-) und erhielt durch „Verlag“ Wertes der Ausgabe - Array zuweisen Filme . Wir können das Assing "Abonnement" (auf: \ .movies, on: self) nur anwenden, wenn der "Herausgeber" keinen Fehler ausgibt , das heißt, er hat den Fehlertyp " Nie" . Wie kann man das erreichen? Verwenden Sie den Operator replaceError (with: []) , der alle Fehler durch ein leeres Filmarray von Filmen ersetzt .Das heißt, die erste einfachere Version unserer SwiftUI- Anwendung zeigt dem Benutzer keine Informationen über mögliche Fehler an.Jetzt, da wir View Model
für unsere Filme haben, können wir anfangen zu kreieren UI
. In der Datei ContentView.swift unsere View Model
wie @EnvironmentObject Variable var moviesViewModel und ersetzt Text ( «Hallo, Welt!» ) Aufdem Text ( "\ (moviesViewModel.indexEndpoint)") , das zeigt einfach die Index indexEndpoint Variante Filmsammlung.
Standardmäßig in unserem View Model
Index der Sammlung indexEndpoint = 2 , so dass wir zu Beginn der Anwendung Notwendigkeit haben Filme zu sehen , die bald auf dem Bildschirm kommen werden ( nächste ):
Dann fügen wirUI
Elemente, die steuern, welche Filmsammlung wir anzeigen möchten. Dies ist Stepper :
... und Picker :
Beide verwenden den "Publisher" $ moviesViewModel.indexEndpoint von uns View Model
und mit einem von ihnen (jedenfalls mit welchem) können wir die Sammlung der Filme auswählen, die wir benötigen:
Dann fügen wir die Liste der empfangenen Filme mit List und ForEach und minimalen Attributen hinzu Film selbst Film :
Liste der angezeigten Filme moviesViewModel.movies, die wir auch aus unseren Filmen entnehmenView Model
:
Wir verwenden NICHT den $ publisher $ moviesViewModel.movies mit dem $ -Zeichen, weil wir in dieser Filmliste nichts bearbeiten werden. Wir verwenden die übliche Eigenschaft moviesViewModel.movies .Sie können eine Liste der Filme interessanter machen , wenn die Anzeige in jeder Zeile der Liste Film - Poster Film entspricht, URL
die in der Struktur präsentiert Film :
Wir haben diese Gelegenheit zu leihen Thomas Ricouard seiner perfekten Design MovieSwiftUI .Wie beim Laden von Filmen haben wir für das UIImage- Image den ImageService- Service , der die fetchImage- Methode mit Combine implementiertRückgabe des "Herausgebers" AnyPublisher <UIImage?, Never> :
... und der letzten Klasse ImageLoader: ObservableObject , die das ObservableObject- Protokoll mit der @Published- Eigenschaft image: UIImage? implementiert. :
Die einzige Anforderung, die das ObservableObject- Protokoll stellt, ist das Vorhandensein der objectWillChange- Eigenschaft . SwiftUI verwendet diese Eigenschaft, um zu verstehen, dass sich in der Instanz dieser Klasse etwas geändert hat, und aktualisiert in diesem Fall alle Ansichten , die von der Instanz dieser Klasse abhängig sind. Normalerweise erstellt der Compiler automatisch eine objectWillChange- Eigenschaft , und alle @Published- Eigenschaften benachrichtigen diese ebenfalls automatisch. In einigen exotischen Situationen können Sie ein objectWillChange manuell erstellen und über die Änderungen benachrichtigen. Wir haben so einen Fall.In der ImageLoader- Klassehaben wir eine einzige @Published- Eigenschaft var image: UIImage? .
Es ist in dieser witzige Umsetzung Eingang und Ausgang, auf der Instanz Initialisierung Imageloader , wir die „Publisher“ verwenden $ Bild und „Abonnement“ ihm den Anruf loadimage () , welche Lasten erforderlich , um uns zu Bild Plakat richtige Größe Größe und Abtretungs es @Veröffentlicht in der Eigenschaft var image: UIImage? .
Wir werden objectWillChange über diese Änderungen informieren .Wir können viele solcher Bilder in der Tabelle haben, was zu erheblichen Zeitkosten führt. Daher verwenden wir das Caching von imageLoader- Instanzen der ImageLoader- Klasse :
Wir haben eine spezielle Ansicht , um das MoviePosterImage - Filmposter abzuspielen :
... und wir werden sie verwenden, wenn wir eine Liste von Filmen in unserer Hauptinhaltsansicht anzeigen :
Der Code für die SwiftUI- basierte Anwendung ohne Fehleranzeige befindet sich bei Github im Ordner CombineFetchAPI-NOError
.Zeigen Sie Fehler beim asynchronen Remote-Movie-Abruf an.
Bisher haben wir keine Fehler verwendet oder angezeigt, die bei der asynchronen Remote-Auswahl von Filmen von der TMDb- Website auftreten . Obwohl die von uns verwendete Funktion movieAPI.fetchMovies (from endpoint: Endpoint) dies ermöglicht, gibt sie den "Publisher" Future <[Movie, MovieStoreAPIError]> zurück .Um Fehler zu berücksichtigen, fügen wir unserer View Model
noch weiteren @Published- Eigenschaft moviesError hinzu: MovieStoreAPIError? das ist der fehler. Dies ist eine optionale Eigenschaft, deren Anfangswert null ist , was der Abwesenheit eines Fehlers entspricht:
Um diesen Fehler zu erhalten, muss moviesError angegeben werden, müssen wir die Initialisierung der Klasse MoviesViewModel geringfügig ändern und einen komplizierteren Sink- Subskribenten verwenden : Der
Fehler moviesError kann angezeigt werden, UI
wenn er nicht gleich Null ist ...
using AlertView :
Diesen Fehler haben wir simuliert, indem wir einfach den richtigen API
Schlüssel entfernt haben:
Code für eine Anwendung, die auf SwiftUI mit basiert Fehleranzeige finden Sie bei Github im Ordner CombineFetchAPI-Error .Wenn Sie ursprünglich keine Fehler behandeln wollten, können Sie auf Future <[Movie], MovieStoreAPIError> verzichten und das Übliche zurückgebenAnyPublisher <[Movie], Never> in der fetchMoviesLight- Methode :
Das Fehlen von Fehlern ( Never ) ermöglicht die Verwendung einer sehr einfachen "Subscriber" -Zuweisung (an: \ .movies, on: self) :
Alles funktioniert wie zuvor:
Fazit
Die Verwendung des Combine- Frameworks zur Verarbeitung einer zeitlich asynchron erscheinenden Folge von Werten ist sehr einfach und unkompliziert. Die Betreiber, die Combine anbietet, sind leistungsstark und flexibel. Mit Combine können wir das Schreiben von komplexem asynchronem Code vermeiden, indem wir die vorgelagerte Kette von Publishern , Publishern , Operatoren und integrierten Abonnenten verwenden . Combine ist auf einer niedrigeren Ebene als Foundation gebaut , benötigt in vielen Fällen keine Foundation und hat eine erstaunliche Leistung.
SwiftUI ist auch stark an Combine gebunden<dank seines @ObservableObject , @Binding und @EnvironmentObject .iOS
Die Entwickler haben lange auf ein solches Apple
offizielles Framework gewartet , und dieses Jahr war es endlich soweit.Referenzen:
Remote-Async-API mit Apple Combine Framework abrufentry! Swift NYC 2019 - Erste Schritte mit Combine"Das ultimative Combine-Framework-Tutorial in Swift".Combine: Asynchrone Programmierung mit SwiftIntroducing Combine - WWDC 2019 - Videos - Apple Developer. Sitzung 722(Zusammenfassung der Sitzung 722 "Einführung in das Kombinieren" auf Russisch)In der Praxis kombinieren - WWDC 2019 - Videos - Apple Developer. Sitzung 721(Zusammenfassung der Sitzung 721 "Praktische Anwendung von Combine" auf Russisch)SwiftUI & Combine: Together is better. Warum Sie mit SwiftUI und Combine bessere Apps erstellen können.MovieSwiftUI .Visualisiere Combine Magic mit SwiftUI Teil 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