API für asynchrones Remote-Abrufen mit Apple Combine



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 :



UISwiftUI :



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 SwiftProtokollen 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 Modelder 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 Modelfür unsere Filme haben, können wir anfangen zu kreieren UI. In der Datei ContentView.swift unsere View Modelwie @EnvironmentObject Variable var moviesViewModel und ersetzt Text ( «Hallo, Welt!» ) Auf
dem Text ( "\ (moviesViewModel.indexEndpoint)") , das zeigt einfach die Index indexEndpoint Variante Filmsammlung.



Standardmäßig in unserem View ModelIndex 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 wirUIElemente, die steuern, welche Filmsammlung wir anzeigen möchten. Dies ist Stepper :



... und Picker :



Beide verwenden den "Publisher" $ moviesViewModel.indexEndpoint von uns View Modelund 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, URLdie 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 Modelnoch 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, UIwenn er nicht gleich Null ist ...



using AlertView :



Diesen Fehler haben wir simuliert, indem wir einfach den richtigen APISchlü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 .
iOSDie Entwickler haben lange auf ein solches Appleoffizielles Framework gewartet , und dieses Jahr war es endlich soweit.

Referenzen:

Remote-Async-API mit Apple Combine Framework abrufen
try! Swift NYC 2019 - Erste Schritte mit Combine
"Das ultimative Combine-Framework-Tutorial in Swift".

Combine: Asynchrone Programmierung mit Swift

Introducing 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 4

Visualize Combine Magic with SwiftUI — Part 5

Getting Started With the Combine Framework in Swift
Transforming Operators in Swift Combine Framework: Map vs FlatMap vs SwitchToLatest
Combine's Future
Using Combine
URLSession and the Combine framework

Source: https://habr.com/ru/post/de476678/


All Articles