Das Abfragen HTTPist eine der wichtigsten Fähigkeiten, die Sie bei der Entwicklung von iOSAnwendungen erwerben müssen . In früheren Versionen Swift(vor Version 5) haben Sie, unabhängig davon, ob Sie diese Anforderungen von Grund auf neu generiert oder das bekannte Alamofire- Framework verwendet haben , komplexen und verwirrenden Code vom callback Typ erhalten completionHandler: @escaping(Result<T, APIError>) -> Void.Das Erscheinen im Swift 5neuen Rahmen der funktionalen reaktiven Programmierung Combinein Verbindung mit der bestehenden URLSessionund Codablebietet Ihnen alle Werkzeuge, die zum unabhängigen Schreiben von sehr kompaktem Code zum Abrufen von Daten aus dem Internet erforderlich sind.In diesem Artikel werden Combinewir gemäß dem Konzept „Verlage“ erstellen.PublisherDaten aus dem Internet auszuwählen, die wir in Zukunft problemlos „abonnieren“ und beim Entwerfen UImit UIKitund mit Hilfe verwenden können SwiftUI.Da SwiftUIes prägnanter und effektiver aussieht, weil die Aktion von "Publishern" Publishernicht nur auf Beispieldaten beschränkt ist, sondern sich bis zur Steuerung der Benutzeroberfläche erstreckt ( UI). Tatsache ist, dass SwiftUIdie Datentrennung View unter Verwendung von ObservableObjectKlassen mit @PublishedEigenschaften durchgeführt wird, deren Änderungen SwiftUIAUTOMATISCH überwacht und vollständig neu gezeichnet werden View.In diesen ObservableObjectKlassen können Sie ganz einfach eine bestimmte Geschäftslogik der Anwendung einfügen, wenn einige davon@Published Eigenschaften sind das Ergebnis von synchronen und / oder asynchronen Transformation andere @Published Eigenschaften auf, die direkt an eine solche „aktive“ Elemente der Benutzerschnittstelle (geändert werden kann UI) als Textkästen TextField, Picker, Stepper, Toggleusw.Um zu verdeutlichen, worum es geht, werde ich konkrete Beispiele nennen. Jetzt bieten viele Dienste wie NewsAPI.org und Hacker News Nachrichtenaggregatoren an , mit denen Benutzer je nach Interesse unterschiedliche Artikelsätze auswählen können. Im Fall des NewsAPI.org- Nachrichtenaggregators kann es sich um die neuesten Nachrichten oder Nachrichten in einer bestimmten Kategorie handeln - "Sport", "Gesundheit", "Wissenschaft", "Technologie", "Unternehmen" oder Nachrichten aus einer bestimmten Informationsquelle "CNN". , ABC-Nachrichten, Bloomberg usw. Der Benutzer "drückt" normalerweise seine Wünsche nach Dienstleistungen in der Form aus Endpoint, die die für ihn notwendige bildet URL.Mit dem Framework Combinekönnen Sie alsoObservableObject Klassen, die einmal einen sehr kompakten Code (in den meisten Fällen nicht mehr als 10-12 Zeilen) verwenden, um eine synchrone und / oder asynchrone Abhängigkeit der Artikelliste Endpointin Form eines "Abonnements" von "passiven" @Published Eigenschaften zu "aktiven" @Published Eigenschaften zu bilden. Dieses "Abonnement" gilt während des gesamten "Lebenszyklus" der ObservableObject Klasseninstanz. Und dann SwiftUI geben Sie dem Benutzer die Möglichkeit, nur die "aktiven" @Published Eigenschaften in dem Formular zu verwalten Endpoint, dh WAS er sehen möchte: ob es sich um Artikel mit den neuesten Nachrichten oder Artikel im Abschnitt "Gesundheit" handelt. Das Erscheinungsbild der Artikel selbst mit den neuesten Nachrichten oder Artikeln im Abschnitt "Gesundheit" UI wird von diesen ObservableObject Klassen und ihren "passiven" @ veröffentlichten Eigenschaften AUTOMATISCH bereitgestellt . In CodeSwiftUI Sie müssen niemals explizit eine Auswahl von Artikeln anfordern, da ObservableObjectKlassen, die eine Rolle spielen , für ihre korrekte und synchrone Anzeige auf dem Bildschirm verantwortlich sind View Model. In einer Reihe von Artikelnwerde ich Ihnen zeigen, wie dies mit NewsAPI.org und Hacker News sowie der TMDb - Filmdatenbank funktioniert . In allen drei Fällen funktioniert ungefähr das gleiche Verwendungsmuster Combine, da Sie bei Anwendungen dieser Art immer LISTEN von Filmen oder Artikeln erstellen müssen, die dazugehörigen „BILDER“ (Bilder) auswählen und die Datenbanken nach den benötigten Filmen oder Artikeln über die Suchleiste suchen.Beim Zugriff auf solche Dienste können Fehler auftreten, z. B. weil Sie den falschen Schlüssel angegeben API-keyoder die zulässige Anzahl von Anforderungen oder etwas anderem überschritten haben. Sie müssen diese Art von Fehler behandeln, da sonst die Gefahr besteht, dass der Benutzer mit einem leeren Bildschirm völlig ratlos bleibt. Daher müssen Sie in der Lage sein, nicht nur Combine Daten aus dem Internet mithilfe von auszuwählen , sondern auch Fehler zu melden, die während der Probenahme auftreten können, und deren Erscheinungsbild auf dem Bildschirm zu verwalten.Wir beginnen mit der Entwicklung unserer Strategie, indem wir eine Anwendung entwickeln, die mit dem NewsAPI.org- Nachrichtenaggregator interagiert . Ich muss sagen, dass es in dieser Anwendung SwiftUIin einem minimalen Umfang ohne Schnickschnack verwendet wird und nur, um zu zeigen, wie es Combinemit seinen "Verlegern" geht.Publisherund "Abonnement" sind Subscriptionbetroffen UI.Es wird empfohlen, dass Sie sich auf der NewsAPI.org- Website registrieren und den Schlüssel erhalten, der APIzum Ausfüllen aller Anfragen an den NewsAPI.org- Dienst erforderlich ist . Sie müssen es in der Datei NewsAPI.swift in der Struktur platzieren APIConstants.Der Anwendungscode für diesen Artikel befindet sich auf Github .NewsAPI.org Service Data Model und API
Mit dem Dienst NewsAPI.org können Sie Informationen zu aktuellen Nachrichtenartikeln [Article]und deren Quellen auswählen [Source]. Unser Datenmodell wird sehr einfach sein, es befindet sich in der Datei Articles.swift :import Foundation
struct NewsResponse: Codable {
let status: String?
let totalResults: Int?
let articles: [Article]
}
struct Article: Codable, Identifiable {
let id = UUID()
let title: String
let description: String?
let author: String?
let urlToImage: String?
let publishedAt: Date?
let source: Source
}
struct SourcesResponse: Codable {
let status: String
let sources: [Source]
}
struct Source: Codable,Identifiable {
let id: String?
let name: String?
let description: String?
let country: String?
let category: String?
let url: String?
}
Der Artikel Articleenthält die Kennung id, den Titel title, die Beschreibung description, den Autor author, die URL des „Bildes“ urlToImage, das Veröffentlichungsdatum publishedAtund die Veröffentlichungsquelle source. Über den Artikeln [Article]befindet sich ein Add- NewsResponseIn, an dem wir nur an der Eigenschaft interessiert sind articles, bei der es sich um eine Reihe von Artikeln handelt. Die Stammstruktur NewsResponseund Struktur Articlesind Codable, die es uns ermöglichen, buchstäblich zwei Codezeilen zu verwenden, die die JSONDaten in das Modell decodieren . Die Struktur Article sollte auch sein Identifiable, wenn wir es uns leichter machen wollen, eine Reihe von Artikeln [Article]als Liste List in anzuzeigen SwiftUI. Das Protokoll Identifiable erfordert das Vorhandensein einer Eigenschaftiddie wir mit einer künstlichen eindeutigen Kennung versehen UUID().Die Informationsquelle Sourceenthält eine Kennung id, einen Namen name, eine Beschreibung description, ein Land country, eine Veröffentlichungsquellenkategorie categoryund eine Site-URL url. Über den Informationsquellen [Source] befindet sich ein Add- SourcesResponseIn, an dem wir nur an einer Eigenschaft interessiert sind sources, bei der es sich um eine Reihe von Informationsquellen handelt. Die Wurzelstruktur SourcesResponseund Struktur Sourceist Codable, die es uns sehr leicht ermöglicht , um die zu dekodieren JSONDaten in ein Modell. Die Struktur Source sollte auch sein Identifiable, wenn wir die Anzeige eines Arrays von Informationsquellen [Source]in Form einer Liste List in erleichtern möchtenSwiftUI. Das Protokoll Identifiableerfordert das Vorhandensein der Eigenschaft id, die wir bereits haben, so dass kein zusätzlicher Aufwand von uns erforderlich ist. Überlegen Sienun, was wir APIfür den NewsAPI.org- Dienst benötigen , und platzieren Sie es in der NewsAPI.swift- Datei . Der zentrale Teil von uns API ist eine Klasse NewsAPI, die zwei Methoden zur Auswahl von Daten aus dem NewsAPI.org- Nachrichtenaggregator vorstellt - Artikel [Article]und Informationsquellen [Source]:fetchArticles (from endpoint: Endpoint) -> AnyPublisher<[Article], Never> - Auswahl der Artikel [Article]anhand des Parameters endpoint,fetchSources (for country: String) -> AnyPublisher<[Source], Never>- Eine Auswahl von Informationsquellen [Source]für ein bestimmtes Land country.
Diese Methoden geben nicht nur eine Reihe von Artikeln [Article] oder eine Reihe von Informationsquellen zurück [Source], sondern auch die entsprechenden "Herausgeber" des Publisher neuen Frameworks Combine. Beide Herausgeber geben keinen Fehler zurück. NeverWenn dennoch ein Stichproben- oder Codierungsfehler aufgetreten ist, wird ein leeres Array von Artikeln [Article]() oder Informationsquellen zurückgegeben, [Source]()ohne dass eine Meldung angezeigt wird, warum diese Arrays leer waren. Welche Artikel oder Informationsquellen wir vom NewsAPI.org- Server auswählen möchten , geben wir anhand der Aufzählung anenum Endpoint:enum Endpoint {
case topHeadLines
case articlesFromCategory(_ category: String)
case articlesFromSource(_ source: String)
case search (searchFilter: String)
case sources (country: String)
var baseURL:URL {URL(string: "https://newsapi.org/v2/")!}
func path() -> String {
switch self {
case .topHeadLines, .articlesFromCategory:
return "top-headlines"
case .search,.articlesFromSource:
return "everything"
case .sources:
return "sources"
}
}
}
Das:- die neuesten Nachrichten
.topHeadLines, - Nachrichten einer bestimmten Kategorie (Sport, Gesundheit, Wissenschaft, Wirtschaft, Technologie)
.articlesFromCategory(_ category: String), - Nachrichten aus einer bestimmten Informationsquelle (CNN, ABC News, Fox News usw.)
.articlesFromSource(_ source: String), - Nachrichten
.search (searchFilter: String), die eine bestimmte Bedingung erfüllen searchFilter, - Informationsquellen
.sources (country:String)für ein bestimmtes Land country.
Um die Initialisierung der von uns benötigten Option zu erleichtern, fügen wir der Aufzählung für verschiedene Listen von Artikeln und Informationsquellen je nach Index und Zeichenfolge einen EndpointInitialisierer hinzu init?, der für verschiedene Aufzählungsoptionen unterschiedliche Bedeutungen hat:index textinit? (index: Int, text: String = "sports") {
switch index {
case 0: self = .topHeadLines
case 1: self = .search(searchFilter: text)
case 2: self = .articlesFromCategory(text)
case 3: self = .articlesFromSource(text)
case 4: self = .sources (country: text)
default: return nil
}
}
Kehren wir zur Klasse zurück NewsAPI und betrachten wir die erste Methode genauer fetchArticles (from endpoint: Endpoint)-> AnyPublisher<[Article], Never>, bei der Artikel [Article]anhand des Parameters ausgewählt werden endpointund kein Fehler zurückgegeben wird - Never:func fetchArticles(from endpoint: Endpoint) -> AnyPublisher<[Article], Never> {
guard let url = endpoint.absoluteURL else {
return Just([Article]()).eraseToAnyPublisher()
}
return
URLSession.shared.dataTaskPublisher(for:url)
.map{$0.data}
.decode(type: NewsResponse.self,
decoder: APIConstants .jsonDecoder)
.map{$0.articles}
.replaceError(with: [])
.receive(on: RunLoop.main)
.eraseToAnyPublisher()
}
- Wenn dies auf der Grundlage des
endpoint Formulars URLfür die Anforderung der gewünschten Artikelliste endpoint.absoluteURLnicht möglich ist, geben Sie ein leeres Array von Artikeln zurück[Article]() - «»
dataTaskPublisher(for:), Output (data: Data, response: URLResponse), Failure - URLError, map { } (data: Data, response: URLResponse) data, JSON data , NewsResponse, articles: [Atricle]map { } - articles, - -
[ ], main , UI,- «» «»
eraseToAnyPublisher() AnyPublisher.
Die Auswahl der Informationsquellen wird der zweiten Methode zugewiesen - fetchSources (for country: String) -> AnyPublisher<[Source], Never>dies ist eine exakte semantische Kopie der ersten Methode, außer dass [Article]wir diesmal anstelle von Artikeln Informationsquellen auswählen [Source]:func fetchSources() -> AnyPublisher<[Source], Never> {
guard let url = Endpoint.sources.absoluteURL else {
return Just([Source]()).eraseToAnyPublisher()
}
return
URLSession.shared.dataTaskPublisher(for:url)
.map{$0.data}
.decode(type: SourcesResponse.self,
decoder: APIConstants .jsonDecoder)
.map{$0.sources}
.replaceError(with: [])
.receive(on: RunLoop.main)
.eraseToAnyPublisher()
}
Es gibt uns den "Herausgeber" AnyPublisher <[Source], Never>mit einem Wert in Form eines Arrays von Informationsquellen [Source] und dem Fehlen eines Fehlers zurück Never (im Fehlerfall wird ein leeres Array von Quellen zurückgegeben [ ]).Wir werden den gemeinsamen Teil dieser beiden Methoden herausgreifen und ihn als eine GenericFunktion anordnen fetch(_ url: URL) -> AnyPublisher<T, Error>, die den Generic"Herausgeber" zurückgibt, AnyPublisher<T, Error>basierend auf URL:
func fetch<T: Decodable>(_ url: URL) -> AnyPublisher<T, Error> {
URLSession.shared.dataTaskPublisher(for: url)
.map { $0.data}
.decode(type: T.self, decoder: APIConstants.jsonDecoder)
.receive(on: RunLoop.main)
.eraseToAnyPublisher()
}
Dies vereinfacht die beiden vorherigen Methoden:
func fetchArticles(from endpoint: Endpoint)
-> AnyPublisher<[Article], Never> {
guard let url = endpoint.absoluteURL else {
return Just([Article]()).eraseToAnyPublisher()
}
return fetch(url)
.map { (response: NewsResponse) -> [Article] in
return response.articles }
.replaceError(with: [Article]())
.eraseToAnyPublisher()
}
func fetchSources(for country: String)
-> AnyPublisher<[Source], Never> {
guard let url = Endpoint.sources(country: country).absoluteURL
else {
return Just([Source]()).eraseToAnyPublisher()
}
return fetch(url)
.map { (response: SourcesResponse) -> [Source] in
response.sources }
.replaceError(with: [Source]())
.eraseToAnyPublisher()
}
Die so erhaltenen "Verlage" liefern nichts, bis jemand sie "abonniert". Wir können dies beim Entwerfen tun UI."Verlage" Publisherwie View Model in SwiftUI. Liste der Artikel.
Nun ein wenig über die Logik des Funktionierens SwiftUI.Die SwiftUI einzige Abstraktion der „externen Änderungen“, auf die sie reagieren, Viewsind die „Herausgeber“ Publisher. Unter „externen Änderungen“ können Sie einen Timer Timer, eine Benachrichtigung mit NotificationCenter oder Ihr Modellobjekt verstehen , das mithilfe des Protokolls ObservableObjectin eine externe einzelne „Quelle der Wahrheit“ umgewandelt werden kann. Zu gewöhnlichen "Verlegern" geben Timeroder NotificationCenter Viewreagieren mit der Methode onReceive (_: perform:). Ein Beispiel für die Verwendung des "Herausgebers" werden Timerwir später im dritten Artikel über die Erstellung einer Anwendung für Hacker News vorstellen .In diesem Artikel konzentrieren wir uns darauf, wie wir unser Modell für erstellen SwiftUIexterne "Quelle der Wahrheit" (Quelle der Wahrheit).Schauen wir uns zunächst an, wie die SwiftUIresultierenden „Herausgeber“ in einem bestimmten Beispiel für die Anzeige verschiedener Arten von Artikeln funktionieren sollten:.topHeadLines- die neuesten Nachrichten, .articlesFromCategory(_ category: String) - Nachrichten für eine bestimmte Kategorie, .articlesFromSource(_ source: String) - Nachrichten für eine bestimmte Informationsquelle, .search (searchFilter: String) - Nachrichten, die von einer bestimmten Bedingung ausgewählt wurden.
Je nachdem, welchen EndpointBenutzer Sie articlesauswählen , müssen wir die Liste der von NewsAPI.org ausgewählten Artikel aktualisieren . Zu diesem Zweck erstellen wir eine sehr einfache Klasse ArticlesViewModel, die ein Protokoll ObservableObject mit drei @PublishedEigenschaften implementiert : 
@Published var indexEndpoint: Int — Endpoint ( «», View), @Published var searchString: String — , ( «», View TextField),@Published var articles: [Article] - ( «», NewsAPI.org, «»).
Sobald wir @Published Eigenschaften indexEndpoint oder festlegen searchString, können wir sie sowohl als einfache Eigenschaften indexEndpoint als searchStringauch als "Herausgeber" $indexEndpointund verwenden $searchString.In einer Klasse ArticlesViewModelkönnen Sie nicht nur die für uns interessanten Eigenschaften deklarieren, sondern auch die Geschäftslogik ihrer Interaktion vorschreiben. Zu diesem Zweck können wir beim Initialisieren einer Instanz einer Klasse ArticlesViewModel in initein "Abonnement" erstellen, das über den gesamten "Lebenszyklus" der Instanz der Klasse wirkt ArticlesViewModelund die Abhängigkeit der Artikelliste vom articlesIndex indexEndpoint und der Suchzeichenfolge reproduziert searchString.Dazu Combineerweitern wir die Kette der "Publisher" $indexEndpoint und $searchStringgeben "Publisher" aus.AnyPublisher<[Article], Never>deren Wert ist eine Liste von Artikeln articles. Dann "abonnieren" wir es mit dem Operator assign (to: \.articles, on: self)und erhalten die Liste der Artikel, die wir articles als "Ausgabe" -Eigenschaft benötigen @Published , die definiert UI.Wir müssen die Kette NICHT einfach aus den Eigenschaften indexEndpointund searchString, nämlich aus den "Publishern" ziehen $indexEndpointund $searchStringdie an der Erstellung UImit teilnehmen, SwiftUIund wir werden sie dort mit den Elementen der Benutzeroberfläche Pickerund ändern TextField.Wie machen wir das?Wir haben bereits eine Funktion in unserem Arsenal fetchArticles (from: Endpoint), die sich in der Klasse befindet NewsAPIund AnyPublisher<[Article], Never>je nach Wert einen "Herausgeber" zurückgibtEndpointund wir können die Werte der "Herausgeber" nur irgendwie nutzen $indexEndpointund $searchStringsie in ein Argument für endpointdiese Funktion verwandeln . Kombinieren Sie zunächst die "Verlage" $indexEndpoint und $searchString. Dazu Combineexistiert der Operator Publishers.CombineLatest :
Um einen neuen "Publisher" basierend auf den vom vorherigen "Publisher" empfangenen Daten zu erstellen Combine , wird der Operator verwendet flatMap:
Als nächstes "abonnieren" wir diesen neu empfangenen "Publisher" mit einem sehr einfachen "Abonnenten" assign (to: \.articles, on: self)und weisen den empfangenen von " Publisher "-Wert für das @Published Array articles:
Wir haben gerade einen init( )ASYNCHRONOUS" Publisher "erstellt und ihn als Ergebnis von" abonniert "AnyCancellable"Abonnement" und dies ist leicht zu überprüfen, wenn wir unser "Abonnement" konstant halten let subscription:
Die Haupteigenschaft eines AnyCancellable"Abonnements" ist, dass der von ihm belegte Speicher automatisch freigegeben wird, sobald es seinen Gültigkeitsbereich verlässt. Daher wird init( ) dieses "Abonnement" gelöscht ARC, sobald es abgeschlossen ist , ohne Zeit zu haben, die empfangenen asynchronen Informationen mit einer Zeitverzögerung dem Array zuzuweisen articles. Asynchrone Informationen können im wörtlichen Sinne einfach nirgendwo "landen", "die Erde ist unter ihren Füßen hervorgegangen".Um ein solches "Abonnement" zu speichern, muss eine init() Variable jenseits des Initialisierers erstellt werden var cancellableSet, die unser AnyCancellable"Abonnement" während des gesamten "Lebenszyklus" der Klasseninstanz in dieser Variablen behält ArticlesViewMode. Daher entfernen wir die Konstante let subscriptionund merken uns unser AnyCancellable"Abonnement" in der Variablen cancellableSetmithilfe des Operators .store ( in: &self.cancellableSet):
"Abonnement" für den ASYNCHRONOUS "Publisher", in dem wir erstellt init( )haben, bleibt während des gesamten "Lebenszyklus" der Klasseninstanz erhalten ArticlesViewModel.Wir können die Bedeutung von "Herausgeber" $indexEndpointund / oder willkürlich ändern searchStringund dank des erstellten "Abonnements" immer eine Reihe von Artikeln haben, die den Werten dieser beiden Herausgeber articlesohne zusätzlichen Aufwand entsprechen. Diese ObservableObjectKlasse wird normalerweise aufgerufen View Model.Um die Anzahl der Aufrufe des Servers beim Eingeben einer Suchzeichenfolge zu verringern searchString, sollten wir nicht den "Herausgeber" der Suchzeichenfolge selbst verwenden $searchStringund seine modifizierte Version validString:
Nachdem wir nun View Modelunsere Artikel haben, beginnen wir mit der Erstellung der Benutzeroberfläche ( UI). Bei SwiftUIder Synchronisierung Viewmit dem ObservableObject Modell wird eine @ObservedObjectVariable verwendet , die auf eine Instanz der Klasse dieses Modells verweist. Dieses Paar - die ObservableObject Klasse und die @ObservedObjectVariable, die auf die Instanz dieser Klasse verweisen - steuert die Änderung in der Benutzeroberfläche ( UI) in SwiftUI.Wir fügen der Struktur eine ContentView Instanz der Klasse ArticleViewModel in Form einer Variablen hinzu var articleViewModelund ersetzen sie Text ("Hello, World!")durch eine Liste von Artikeln, ArticlesListin die wir die Artikel articlesViewModel.articlesaus unserer Liste einfügen View Model. Als Ergebnis erhalten wir eine Liste von Artikeln für einen festen und einen Standardindex, dh indexEndpoint = 0für die .topHeadLines neuesten Nachrichten:
Fügen Sie unserem Bildschirm ein UIElement hinzu, um zu steuern, welche Artikelgruppe angezeigt werden soll. Wir werden die PickerIndexänderung verwenden $articlesViewModel.indexEndpoint. Das Vorhandensein eines Symbols ist $obligatorisch, da dies eine Änderung des vom @Published "Herausgeber" angegebenen Werts bedeutet . Das "Abonnement" für diesen "Herausgeber" wird sofort ausgelöst, worauf wir uns eingelassen init ()haben. Die "Ausgabe" @Published"Herausgeber" articles ändert sich und es wird eine andere Liste von Artikeln auf dem Bildschirm angezeigt:
Auf diese Weise können wir Arrays von Artikeln für alle drei Optionen empfangen - "topHeadLines", "Suche" "Und" aus der Kategorie ":
... aber für eine feste und standardmäßige Suchzeichenfolge searchString = "sports"(wo erforderlich):
Für die Option müssen "search" Sie dem Benutzer jedoch ein Textfeld SearchViewzur Eingabe der Suchzeichenfolge zur Verfügung stellen:
Infolgedessen kann der Benutzer anhand der eingegebenen Suchzeichenfolge nach Nachrichten suchen:
Für die Option "from category" muss der Benutzer angegeben werden die Möglichkeit , eine Kategorie zu wählen , und wir beginnen mit der Kategorie science:
als Ergebnis kann der Benutzer für jede Nachricht von der gewählten Kategorie der Nachrichten suchen - science, health, business, technology:
wir können sehen , wie ein sehr einfaches ObservableObject Modell , das zwei benutzergesteuerte hat @Published Funktionen - indexEndpoint undsearchString- Ermöglicht die Auswahl einer Vielzahl von Informationen auf der NewsAPI.org- Website .Liste der Informationsquellen
Schauen wir uns an, wie der SwiftUI „Herausgeber“ der in der NewsAPI-Klasse empfangenen Informationsquellen funktioniert fetchSources (for country: String) -> AnyPublisher<[Source], Never>.Wir erhalten eine Liste mit Informationsquellen für verschiedene Länder:
... und die Möglichkeit, nach Namen zu suchen:
... sowie detaillierte Informationen über die ausgewählte Quelle: Name, Kategorie, Land, Kurzbeschreibung und Link zur Website:
Wenn Sie auf den Link klicken, gelangen Sie zu deren Website Informationsquelle.Damit dies alles funktioniert, benötigen Sie ein extrem einfaches ObservableObjectModell mit nur zwei benutzergesteuerten @PublishedEigenschaften - searchString und country:
Und wieder verwenden wir dasselbe Schema: beim Initialisieren einer Instanz einer SourcesViewModel Klassenklasse in initWir erstellen ein "Abonnement", das während des gesamten "Lebenszyklus" der Klasseninstanz funktioniert, SourcesViewModelund stellen sicher, dass die Liste der Informationsquellen vom sourcesLand countryund der Suchzeichenfolge abhängt searchString.Mit der Hilfe Combineziehen wir die Kette von den "Herausgebern" $searchString und $countrygeben "Herausgeber" aus AnyPublisher<[Source], Never>, dessen Wert eine Liste von Informationsquellen ist. Wir "abonnieren" es mit dem Operator assign (to: \.sources, on: self), wir erhalten die Liste der Informationsquellen, die wir benötigen sources. und merken Sie sich das empfangene AnyCancellable"Abonnement" in einer Variablen cancellableSetmit dem Operator .store ( in: &self.cancellableSet).Nachdem wir nun View Modelunsere Informationsquellen zur Verfügung haben, können wir mit der Erstellung beginnen UI. B SwiftUIzu synchronisieren ViewcObservableObject Das Modell verwendet eine @ObservedObjectVariable, die auf eine Instanz der Klasse dieses Modells verweist.Fügen Sie der Struktur die ContentViewSources Klasseninstanz SourcesViewModelin Form einer Variablen hinzu var sourcesViewModel, entfernen Sie sie Text ("Hello, World!") und platzieren Sie Ihre eigene Viewfür jede der drei @PublishedEigenschaften sourcesViewModel : - Textfeld
SearchViewfür die Suchleiste searchString, -
Picker für das Land country, - Liste
SourcesList der Informationsquellen
Als Ergebnis erhalten wir, was wir brauchen View:
Auf diesem Bildschirm verwalten wir die Suchzeichenfolge nur über das Textfeld SearchViewund das "Land" mit Picker, und der Rest geschieht AUTOMATISCH.Die Liste der Informationsquellen SourcesListenthält nur minimale Informationen zu jeder Quelle - den Namen source.name und eine kurze Beschreibung source.description:
... Sie können jedoch über den Link, NavigationLinkin dem destinationwir angeben, DetailSourceViewwelche Quelldaten die Informationsquelle sourceund die gewünschte Instanz der Klasse sind ArticlesViewModel, detailliertere Informationen über die ausgewählte Quelle erhalten Holen Sie sich eine Liste seiner Artikel articles:
Sehen Sie, wie elegant wir die Liste der Artikel für die ausgewählte Informationsquelle in der Liste der Quellen erhalten SourcesList. Unser alter Freund hilft uns - eine Klasse, ArticlesViewModelfür die wir beide "Eingabe" @Published-Eigenschaften festlegen müssen :- Index
indexEndpoint = 3, dh eine Option .articlesFromSource (_source:String), die der Auswahl von Artikeln für eine feste Quelle entspricht source, - Zeichenfolge
searchString als Quelle selbst (oder vielmehr als Kennung) source.id :
Wenn Sie sich die gesamte NewsApp- Anwendung ansehen , werden Sie im Allgemeinen nirgendwo feststellen , dass wir ausdrücklich eine Auswahl von Artikeln oder Informationsquellen von der NewsAPI.org- Website anfordern . Wir verwalten nur @Published Daten, erledigen View Model aber unsere Aufgabe: Wir wählen die Artikel und Informationsquellen aus, die wir benötigen.Bild UIImagefür Artikel herunterladen Article.
Das Modell des Artikels Article enthält ein URLdazugehöriges Bild urlToImage:
Auf dieser Grundlage URLmüssen wir in Zukunft die Bilder selbst UIImage von der NewsAPI.org- Website beziehen .Wir sind mit dieser Aufgabe bereits vertraut. Erstellen Sie in der Klasse ImageLoadermithilfe der Funktion fetchImage(for url: URL?) -> AnyPublisher<UIImage?, Never>einen „Herausgeber“ AnyPublisher<UIImage?, Never>mit dem Bildwert UIImage? und ohne Fehler Never(wenn tatsächlich Fehler auftreten, wird das Bild zurückgegeben nil). Sie können diesen "Herausgeber" "abonnieren", um Bilder UIImage? beim Entwerfen der Benutzeroberfläche ( UI) zu erhalten. Die Quelldaten für die Funktion fetchImage(for url: URL?)sind url, die wir haben:
Lassen Sie uns im Detail überlegen, wie die Gründung mit Hilfe des Combine"Herausgebers" abläuft AnyPublisher <UIImage?, Never>, wenn wir wissen url:- wenn
urlgleich nil, kehre zurück Just(nil), - basierend auf der
urlForm des "Herausgebers" dataTaskPublisher(for:), dessen Ausgabewert Outputein Tupel (data: Data, response: URLResponse)und ein Fehler ist Failure- URLError, - Wir nehmen nur Daten
map {}aus dem Tupel (data: Data, response: URLResponse)zur weiteren Verarbeitung dataund Form UIImage, - Wenn bei den vorherigen Schritten ein Rückgabefehler auftritt
nil, - Wir liefern das Ergebnis an den
mainStream, da wir davon ausgehen, dass es im Design weiter verwendet UIwird. - "Löschen" Sie den TYP des "Herausgebers" und geben Sie die Kopie zurück
AnyPublisher.
Sie sehen, dass der Code ziemlich kompakt und gut lesbar ist, es gibt keine callbacks.Beginnen wir mit der Erstellung View Model für das Bild UIImage?. Dies ist eine Klasse ImageLoader, die das Protokoll ObservableObjectmit zwei @PublishedEigenschaften implementiert :@Published url: URL? sind URLBilder@Published var image: UIImage? ist das Bild selbst von NewsAPI.org :
Und wieder ImageLoader müssen wir beim Initialisieren einer Instanz der Klasse die Kette von der Eingabe "Herausgeber" $url zur Ausgabe "Herausgeber" strecken AnyPublisher<UIImage?, Never>, die wir später "abonnieren" und das Bild erhalten, das wir benötigen image:
Wir verwenden den Operator flatMapund einen sehr einfachen "Abonnenten" assign (to: \image, on: self), um sie dem vom "Herausgeber" empfangenen zuzuweisen. "Werte für die Eigenschaft @Published image:
Und wieder in der Variablen " Abonnement "wird mit dem Operator cancellableSet gespeichert . Die Logik dieses „Bild-Downloaders“ besteht darin, dass Sie ein Bild von etwas anderem als dem herunterladen, sofern es nicht vorgeladen wurdeAnyCancellablestore(in: &self.cancellableSet)nil URLimage == nil. Wenn während des Downloadvorgangs ein Fehler festgestellt wird, fehlt das Bild, dh es imagebleibt gleich nil.In SwiftUIzeigen wir das Bild mit Hilfe ArticleImage, die eine Instanz der imageLoader Klasse dafür verwendet ImageLoader. Wenn sein Bild nicht gleich ist nil, wird es mit angezeigt Image (...), aber wenn es gleich ist nil, wird je nachdem, was gleich ist url , entweder nichts angezeigt EmptyView()oder ein Rechteck Rectanglemit rotierendem Text T wird angezeigt ext("Loading..."):
Diese Logik funktioniert für den Fall einwandfrei wenn Sie sicher wissen, dass für url, außer nilSie erhalten ein Bild image, wie es bei der Filmdatenbank der Fall ist TMDb . Bei NewsAPI.org ist der Nachrichtenaggregator anders. Die Artikel einiger Informationsquellen unterscheiden sich vom nil URLBild, aber der Zugriff darauf ist geschlossen, und wir erhalten ein Rechteck Rectanglemit rotierendem Text Text("Loading..."), das niemals ersetzt wird:
Wenn sich das URLBild in dieser Situation von dem Bild unterscheidet nil, kann die Gleichheit des nilBildes imagebedeuten, dass das Bild geladen wird und die Tatsache, dass beim Laden ein Fehler aufgetreten ist und wir nie ein Bild erhalten image. Um zwischen diesen beiden Situationen zu unterscheiden, fügen wir ImageLoader den beiden vorhandenen @PublishedEigenschaften in der Klasse eine weitere hinzu: @Published var noData = false - Dies ist ein boolescher Wert, mit dem wir das Fehlen von Bilddaten aufgrund eines Fehlers bei der Auswahl anzeigen:
Beim Erstellen eines „Abonnements“ erfassen wir initalle Fehler Error, die beim Laden des Bildes auftreten, und akkumulieren deren Vorhandensein in der @PublishedEigenschaft self.noData = true. Wenn der Download erfolgreich war, erhalten wir das Bild image. Wir erstellen den"Publisher" AnyPublisher<UIImage?, Error> auf der Grundlage der url Funktion fetchImageErr (for url: URL?):
Wir beginnen die Erstellung der Methode fetchImageErrmit der Initialisierung des "Publishers" Future, mit dem ein einzelner TYPE-Wert Resultmithilfe eines Abschlusses asynchron abgerufen werden kann . Der Abschluss hat einen Parameter - Promiseder eine Funktion von TYPE ist (Result<Output, Failure>) → Void: Wir werden das
Ergebnis Futurein verwandelnAnyPublisher <UIImage?, Error>mit Hilfe des Operators "TYP löschen" eraseToAnyPublisher().Als nächstes führen wir die folgenden Schritte aus, wobei wir alle möglichen Fehler berücksichtigen (wir werden keine Fehler identifizieren, es ist einfach wichtig zu wissen, dass ein Fehler vorliegt):0. Prüfen Sie urlauf nil und noDataweiter true: Wenn ja, geben Sie den Fehler zurück, wenn nicht, übertragen Sie ihn urlweiter durch Kette,1. Erstellen Sie einen "Herausgeber", dataTaskPublisher(for:)dessen Eingabe - ist url, und der Ausgabewert Outputist ein Tupel (data: Data, response: URLResponse)und ein Fehler URLError.2. Analysieren Sie mit dem tryMap { } resultierenden Tupel (data: Data, response: URLResponse): Wenn es response.statusCodeim Bereich liegt 200...299, nehmen wir zur weiteren Verarbeitung nur die Daten data. Andernfalls "werfen" wir einen Fehler aus (egal was passiert),3. werden wir map { }die Daten konvertieren datazu UIImage,4. liefern das Ergebnis an den mainStrom, da wir davon ausgehen , dass wir es später im Design verwenden UI- wir „subscribe“ auf den empfangene „Verleger“ mit sinkseinen Schließungen receiveCompletionund receiveValue,- 5. , wenn wir receiveCompletion einen Fehler finden in der Schließung error, wir berichten Verwenden Sie es promise (.failure(error))),- 6. im Abschluss receiveValue informieren wir Sie über den erfolgreichen Empfang einer Reihe von Artikeln mit promise (.success($0)),7. wir erinnern uns an das empfangene "Abonnement" in der Variablen cancellableSet, um deren Lebensfähigkeit innerhalb der "Lebensdauer" der Klasseninstanz sicherzustellen ImageLoader,8. wir "löschen" den "Herausgeber" -TYP und geben Sie die Instanz zurück AnyPublisher.Wir kehren dorthin zurück, ArticleImagewo wir die neue @PublishedVariable verwenden werden noData. Wenn keine Bilddaten vorhanden sind, werden wir nichts anzeigen, dhEmptyView () :
Am Ende packen wir alle unsere Möglichkeiten zur Anzeige von Daten aus dem NewsAPI.org- Nachrichtenaggregator in TabView:
Zeigen Sie Fehler beim Abrufen und Dekodieren von JSON-Daten vom NewsAPI.org- Server an .
Beim Zugriff auf den NewsAPI.org- Server können Fehler auftreten, z. B. weil Sie den falschen Schlüssel angegeben haben API-keyoder wenn ein Entwicklertarif, der nichts kostet, die zulässige Anzahl von Anforderungen oder etwas anderem überschritten hat. Gleichzeitig liefert Ihnen der NewsAPI.org- Server den HTTPCode und die entsprechende Meldung:
Diese Art von Serverfehler muss behandelt werden. Andernfalls gerät der Benutzer Ihrer Anwendung in eine Situation, in der der NewsAPI.org- Server plötzlich ohne Grund die Verarbeitung von Anforderungen beendet und der Benutzer mit einem leeren Bildschirm völlig ratlos bleibt .Bisher bei der Auswahl von Artikeln [Article]und Informationsquellen [Source]vom NewsAPI.org- Server wir ignoriert alle Fehler, und im Falle ihres Auftretens zurück leere Arrays [Article]()und als Folge [Source]().Lassen Sie uns zunächst fetchArticles (from endpoint: Endpoint) -> AnyPublisher<[Article], Never> eine NewsAPI andere Methode in der Klasse erstellen , die auf der vorhandenen Artikelauswahlmethode basiert und fetchArticlesErr (from endpoint: Endpoint) -> AnyPublisher<[Article], NewsError>nicht nur ein Array von Artikeln [Article], sondern auch einen möglichen Fehler zurückgibt NewsError:func fetchArticlesErr(from endpoint: Endpoint) ->
AnyPublisher<[Article], NewsError> {
. . . . . . . .
}
Dieses Verfahren sowie die Verfahren fetchArticles, akzeptiert endpoint und gibt den „Verlag“ am Eingang mit einem Wert in der Form einer Reihe von Artikeln [Article], sondern das Fehlen eines Fehlers Neverkönnen wir einen Fehler durch die Aufzählung definiert haben NewsError:
Beginnen wir eine neue Methode , mit der Initialisierung des „Publisher“ zu schaffen Future, das kann sein Verwenden Sie diese Option, um Resultmithilfe eines Abschlusses asynchron einen einzelnen TYPE-Wert abzurufen. Der Abschluss hat einen Parameter - Promiseeine Funktion von TYPE (Result<Output, Failure>) -> Void: Wir werden den
Empfang mit dem Operator "TYPE Erase" Futurein den "Publisher" verwandeln, den wir benötigen . Weiter in der neuen Methode werden wir alle Schritte wiederholen, die wir in der Methode unternommen haben AnyPublisher <[Article], NewsError>eraseToAnyPublisher()fetchArticlesErrfetchArticles, aber wir werden alle möglichen Fehler berücksichtigen:
- 0. endpoint
URL endpoint.absoluteURL , url nil: nil, .urlError, — url , - 1. «»
dataTaskPublisher(for:), — url, Output (data: Data, response: URLResponse) URLError, - 2.
tryMap { } (data: Data, response: URLResponse): response.statusCode 200...299, data. «» .responseError, data, String, , - 3.
JSON , NewsResponse, - 4.
main , UI - «» «»
sink receiveCompletion receiveValue,
- 5.
receiveCompletion error, promise (.failure(...))), - 6.
receiveValue promise (.success($0.articles)),
- 7. «»
var subscriptions, « » NewsAPI, - 8. «» «»
AnyPublisher.
Es ist zu beachten, dass sich der "Herausgeber" von seinem Prototyp dadurch dataTaskPublisher(for:) unterscheidet,dataTask dass er im Falle eines Serverfehlers, wenn er response.statusCode NICHT im Bereich liegt 200...299, weiterhin den erfolgreichen Wert in Form eines Tupels (data: Data, response: URLResponse)und keinen Fehler in der Form liefert (Error, URLResponse?). In diesem Fall sind die tatsächlichen Serverfehlerinformationen in enthalten data. Der "Herausgeber" dataTaskPublisher(for:) liefert einen Fehler, URLErrorwenn auf der Clientseite ein Fehler auftritt (Unfähigkeit, den Server zu kontaktieren, Verbot des Sicherheitssystems ATSusw.).Wenn wir Fehler in anzeigen möchten, SwiftUIbenötigen wir den entsprechenden View Model, den wir aufrufen werden ArticlesViewModelErr:
In der Klasse ArticlesViewModelErr, die das Protokoll implementiert ObservableObject , haben wir diesmal VIER @PublishedEigenschaften:@Published var indexEndpoint: Int — Endpoint ( «», View), @Published var searchString: String — , Endpoint: «» , ( «», View), -
@Published var articles: [Article] - ( «», NewsAPI.org ) -
@Published var articlesError: NewsError? - , NewsAPI.org .
Wenn Sie eine Instanz einer Klasse zu initialisieren ArticlesViewModelErr, müssen wir wieder eine Kette von Eingang „Verlage“ erweitern $indexEndpointund $searchStringdie Ausgabe „Verlag“ AnyPublisher<[Article],NewsError>, auf die wir mit dem „Abonnent“ „unterzeichnet“ sinkund wir haben eine Menge von Artikeln erhalten articlesoder Fehler articlesError.In unserer Klasse haben NewsAPIwir bereits eine Funktion erstellt , die je nach Wert fetchArticlesErr (from endpoint: Endpoint)einen „Herausgeber“ zurückgibt , und wir müssen nur irgendwie die Werte der „Herausgeber“ verwenden und sie in ein Argument für diese Funktion umwandeln . Zunächst werden wir die "Verlage" und . Dazu gibt es einen Operator :AnyPublisher<[Article], NewsError>endpoint$indexEndpoint$searchStringendpoint$indexEndpoint$searchStringCombinePublishers.CombineLatest
Dann müssen wir den Fehlertyp TYPE "publisher" gleich dem erforderlichen setzen NewsError:
Als nächstes wollen wir die Funktion fetchArticlesErr (from endpoint: Endpoint) aus unserer Klasse verwenden NewsAPI. Wie üblich werden wir dies mit Hilfe eines Betreibers tun flatMap, der einen neuen "Herausgeber" auf der Grundlage der vom vorherigen "Herausgeber" empfangenen Daten erstellt:
Dann "abonnieren" wir diesen neu empfangenen "Herausgeber" mit Hilfe eines "Abonnenten" sinkund verwenden seine Schließungen receiveCompletionund receiveValueUm vom „Verlag“ entweder den Wert einer Reihe von Artikeln articlesoder Fehler zu erhalten articlesError:
Natürlich muss das resultierende „Abonnement“ in einer externen init()Variablen gespeichert werden cancellableSet. Andernfalls können wir den Wert nicht asynchron abrufenarticlesoder ein Fehler articlesError nach dem Abschluss init():
Um die Anzahl der Anrufe an den Server zu reduzieren , wenn eine Suchzeichenfolge eingeben searchString, sollten wir nicht den „Verlag“ der Suchleiste selbst $searchString, aber seine modifizierte Version validString:
„Abonnieren“ den ASYNCHRONEN „Verlag“ , dass wir in der geschaffenen init( )Willen bestehen während des gesamten „Lebenszyklus“ der Klasseninstanz fort ArticlesViewModelErr:
Wir fahren mit der Korrektur unserer fort UI, um mögliche Datenabtastfehler darauf anzuzeigen. In SwiftUI verwenden wir in der vorhandenen Struktur ContentVieArticles eine andere, die gerade erhalten wurde View Model, und fügen dem Namen nur die Buchstaben „Err“ hinzu. Dies ist eine Instanz der Klasse. ArticlesViewModelErr, der den Fehler beim Auswählen und / oder Dekodieren von Artikeldaten vom NewsAPI.org- Server „ abfängt“ :
Außerdem fügen wir im Fehlerfall die Anzeige einer Notfallnachricht hinzu Alert .Zum Beispiel, wenn der falsche API-Schlüssel lautet:struct APIConstants {
static let apiKey: String = "API_KEY"
. . . . . . . . . . . . .
}
... dann erhalten wir folgende Meldung:
Wenn das Anforderungslimit erschöpft ist, erhalten wir folgende Meldung: Wenn wir
zur Methode der Artikelauswahl [Article] mit einem möglichen Fehler zurückkehren NewsError, können wir den Code vereinfachen, wenn wir den Generic "Herausgeber" verwenden AnyPublisher<T,NewsError>,, urlder JSONInformationen basierend auf dem Satz asynchron empfängt und direkt platziert im CodableModell T und meldet einen Fehler NewsError:
Wie wir wissen, ist dieser Code sehr einfach zu verwenden, um einen bestimmten „Herausgeber“ zu erhalten, wenn die Quelldaten für urleinen NewsAPI.org-Endpoint Nachrichtenaggregator oder ein NewsAPI.org- Nachrichtenland sind country Informationsquelle, und die Ausgabe erfordert verschiedene Modelle - zum Beispiel eine Liste von Artikeln oder Informationsquellen:

Fazit
Wir haben gelernt , wie einfach es zu erfüllen ist HTTPAnforderungen mit Hilfe Combineseiner URLSession„Herausgeber“ dataTaskPublisherund Codable. Wenn Sie keine Fehler nachverfolgen müssen, erhalten Sie einen sehr einfachen Generic5-zeiligen Code für den "Herausgeber" AnyPublisher<T, Never>, der JSON Informationen asynchron empfängt und basierend auf den folgenden Angaben direkt in das Codable Modell Teinfügt url:
Dieser Code ist sehr einfach zu verwenden, um einen bestimmten Herausgeber abzurufen, wenn die Quelldaten Folgendes url sind: Zum Beispiel Endpointerfordert die Ausgabe verschiedene Modelle - zum Beispiel eine Reihe von Artikeln oder eine Liste von Informationsquellen.Wenn Sie Fehler berücksichtigen müssen, ist der Code für den Generic"Herausgeber" etwas komplizierter, aber dennoch ein sehr einfacher Code ohne Rückrufe:
Können Sie mithilfe der Technologie der HTTPAbfrageausführung mithilfe Combineeinen "Herausgeber" erstellen AnyPublisher<UIImage?, Never>, der asynchron Daten auswählt und ein UIImage-Image empfängt? basierend auf URL. Bild- ImageLoadeDownloader r werden im Speicher zwischengespeichert, um ein wiederholtes asynchrones Abrufen von Daten zu vermeiden.Alle Arten von "Publishern", die Sie erhalten, können sehr einfach in ObservableObject-Klassen "zum Arbeiten gebracht" werden, die ihre @ Publishing-Eigenschaften verwenden, um Ihre mit SwiftUI entworfene Benutzeroberfläche zu steuern. Diese Klassen spielen normalerweise die Rolle des Ansichtsmodells, da sie die sogenannten @ input @ -Published-Eigenschaften haben, die den aktiven UI-Elementen (TextField, Stepper, Picker-Textfelder, Optionsfelder zum Umschalten usw.) und den @Published-Ausgabeeigenschaften entsprechenDiese Idee durchdringt die gesamte in diesem Artikel vorgestellte NewsAPI.org- Nachrichtenaggregator- Anwendung . Es stellte sich heraus, dass dies aus passiven UI-Elementen ( Texttexten, Bildbildern, geometrischen Kreisformen (), Rechteck () usw.) besteht ziemlich universell und wurde verwendet, wennEntwicklung einer Anwendung für die TMDb - Filmdatenbank und den Hacker News- Nachrichtenaggregator , die in zukünftigen Artikeln behandelt werden.Der Anwendungscode für diesen Artikel befindet sich auf Github .PS1. Ich möchte Ihre Aufmerksamkeit auf die Tatsache lenken, dass Sie wissen, dass NavigationLinkder Simulator mit einem Fehler arbeitet , wenn Sie den Simulator für die in diesem Artikel vorgestellte Anwendung verwenden . Sie können verwendenNavigationLinkauf dem Simulator nur 1 Mal. Jene. Sie haben den Link verwendet, sind zurückgegangen, haben auf denselben Link geklickt - und nichts passiert. Bis Sie einen anderen Link verwenden, funktioniert der erste nicht, der zweite ist jedoch nicht mehr zugänglich. Dies wird aber nur am Simulator beobachtet, auf einem realen Gerät funktioniert alles einwandfrei.2. Einige Informationsquellen verwenden httpstattdessen httpsnoch "Bilder" ihrer Artikel. Wenn Sie diese „Bilder“ definitiv sehen möchten, aber die Quelle ihres Erscheinungsbilds nicht steuern können, müssen Sie das Sicherheitssystem so konfigurieren ATS ( App Transport Security), dass diese http„Bilder“ empfangen werden. Dies ist jedoch natürlich keine gute Idee . Sie können sicherere Optionen verwenden .Verweise:
HTTP Swift 5 Combine SwiftUI. 1 .Modern Networking in Swift 5 with URLSession, Combine and Codable.URLSession.DataTaskPublisher’s failure typeCombine: Asynchronous Programming with Swift«SwiftUI & Combine: »Introducing Combine — WWDC 2019 — Videos — Apple Developer. session 722( 722 « Combine» )Combine in Practice — WWDC 2019 — Videos — Apple Developer. session 721( 721 « Combine» )