Das Abfragen HTTP
ist eine der wichtigsten Fähigkeiten, die Sie bei der Entwicklung von iOS
Anwendungen 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 5
neuen Rahmen der funktionalen reaktiven Programmierung Combine
in Verbindung mit der bestehenden URLSession
und Codable
bietet 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 Combine
wir gemäß dem Konzept „Verlage“ erstellen.Publisher
Daten aus dem Internet auszuwählen, die wir in Zukunft problemlos „abonnieren“ und beim Entwerfen UI
mit UIKit
und mit Hilfe verwenden können SwiftUI
.Da SwiftUI
es prägnanter und effektiver aussieht, weil die Aktion von "Publishern" Publisher
nicht nur auf Beispieldaten beschränkt ist, sondern sich bis zur Steuerung der Benutzeroberfläche erstreckt ( UI
). Tatsache ist, dass SwiftUI
die Datentrennung View
unter Verwendung von ObservableObject
Klassen mit @Published
Eigenschaften durchgeführt wird, deren Änderungen SwiftUI
AUTOMATISCH überwacht und vollständig neu gezeichnet werden View
.In diesen ObservableObject
Klassen 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
, Toggle
usw.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 Combine
kö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 Endpoint
in 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 ObservableObject
Klassen, 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-key
oder 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 SwiftUI
in einem minimalen Umfang ohne Schnickschnack verwendet wird und nur, um zu zeigen, wie es Combine
mit seinen "Verlegern" geht.Publisher
und "Abonnement" sind Subscription
betroffen UI
.Es wird empfohlen, dass Sie sich auf der NewsAPI.org- Website registrieren und den Schlüssel erhalten, der API
zum 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 Article
enthält die Kennung id
, den Titel title
, die Beschreibung description
, den Autor author
, die URL des „Bildes“ urlToImage
, das Veröffentlichungsdatum publishedAt
und die Veröffentlichungsquelle source
. Über den Artikeln [Article]
befindet sich ein Add- NewsResponse
In, an dem wir nur an der Eigenschaft interessiert sind articles
, bei der es sich um eine Reihe von Artikeln handelt. Die Stammstruktur NewsResponse
und Struktur Article
sind Codable
, die es uns ermöglichen, buchstäblich zwei Codezeilen zu verwenden, die die JSON
Daten 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 Eigenschaftid
die wir mit einer künstlichen eindeutigen Kennung versehen UUID()
.Die Informationsquelle Source
enthält eine Kennung id
, einen Namen name
, eine Beschreibung description
, ein Land country
, eine Veröffentlichungsquellenkategorie category
und eine Site-URL url
. Über den Informationsquellen [Source]
befindet sich ein Add- SourcesResponse
In, an dem wir nur an einer Eigenschaft interessiert sind sources
, bei der es sich um eine Reihe von Informationsquellen handelt. Die Wurzelstruktur SourcesResponse
und Struktur Source
ist Codable
, die es uns sehr leicht ermöglicht , um die zu dekodieren JSON
Daten 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 Identifiable
erfordert das Vorhandensein der Eigenschaft id
, die wir bereits haben, so dass kein zusätzlicher Aufwand von uns erforderlich ist. Überlegen Sienun, was wir API
fü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. Never
Wenn 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 Endpoint
Initialisierer hinzu init?
, der für verschiedene Aufzählungsoptionen unterschiedliche Bedeutungen hat:index
text
init? (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 endpoint
und 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 URL
für die Anforderung der gewünschten Artikelliste endpoint.absoluteURL
nicht 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 Generic
Funktion 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" Publisher
wie 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, View
sind die „Herausgeber“ Publisher
. Unter „externen Änderungen“ können Sie einen Timer Timer
, eine Benachrichtigung mit NotificationCenter
oder Ihr Modellobjekt verstehen , das mithilfe des Protokolls ObservableObject
in eine externe einzelne „Quelle der Wahrheit“ umgewandelt werden kann. Zu gewöhnlichen "Verlegern" geben Timer
oder NotificationCenter
View
reagieren mit der Methode onReceive (_: perform:)
. Ein Beispiel für die Verwendung des "Herausgebers" werden Timer
wir 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 SwiftUI
externe "Quelle der Wahrheit" (Quelle der Wahrheit).Schauen wir uns zunächst an, wie die SwiftUI
resultierenden „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 Endpoint
Benutzer Sie articles
auswä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 @Published
Eigenschaften 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 searchString
auch als "Herausgeber" $indexEndpoint
und verwenden $searchString
.In einer Klasse ArticlesViewModel
kö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 init
ein "Abonnement" erstellen, das über den gesamten "Lebenszyklus" der Instanz der Klasse wirkt ArticlesViewModel
und die Abhängigkeit der Artikelliste vom articles
Index indexEndpoint
und der Suchzeichenfolge reproduziert searchString
.Dazu Combine
erweitern wir die Kette der "Publisher" $indexEndpoint
und $searchString
geben "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 indexEndpoint
und searchString
, nämlich aus den "Publishern" ziehen $indexEndpoint
und $searchString
die an der Erstellung UI
mit teilnehmen, SwiftUI
und wir werden sie dort mit den Elementen der Benutzeroberfläche Picker
und ändern TextField
.Wie machen wir das?Wir haben bereits eine Funktion in unserem Arsenal fetchArticles (from: Endpoint)
, die sich in der Klasse befindet NewsAPI
und AnyPublisher<[Article], Never>
je nach Wert einen "Herausgeber" zurückgibtEndpoint
und wir können die Werte der "Herausgeber" nur irgendwie nutzen $indexEndpoint
und $searchString
sie in ein Argument für endpoint
diese Funktion verwandeln . Kombinieren Sie zunächst die "Verlage" $indexEndpoint
und $searchString
. Dazu Combine
existiert 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 subscription
und merken uns unser AnyCancellable
"Abonnement" in der Variablen cancellableSet
mithilfe 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" $indexEndpoint
und / oder willkürlich ändern searchString
und dank des erstellten "Abonnements" immer eine Reihe von Artikeln haben, die den Werten dieser beiden Herausgeber articles
ohne zusätzlichen Aufwand entsprechen. Diese ObservableObject
Klasse 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 $searchString
und seine modifizierte Version validString
:
Nachdem wir nun View Model
unsere Artikel haben, beginnen wir mit der Erstellung der Benutzeroberfläche ( UI
). Bei SwiftUI
der Synchronisierung View
mit dem ObservableObject
Modell wird eine @ObservedObject
Variable verwendet , die auf eine Instanz der Klasse dieses Modells verweist. Dieses Paar - die ObservableObject
Klasse und die @ObservedObject
Variable, 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 articleViewModel
und ersetzen sie Text ("Hello, World!")
durch eine Liste von Artikeln, ArticlesList
in die wir die Artikel articlesViewModel.articles
aus unserer Liste einfügen View Model
. Als Ergebnis erhalten wir eine Liste von Artikeln für einen festen und einen Standardindex, dh indexEndpoint = 0
für die .topHeadLines
neuesten Nachrichten:
Fügen Sie unserem Bildschirm ein UI
Element hinzu, um zu steuern, welche Artikelgruppe angezeigt werden soll. Wir werden die Picker
Indexä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 SearchView
zur 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 ObservableObject
Modell mit nur zwei benutzergesteuerten @Published
Eigenschaften - searchString
und country
:
Und wieder verwenden wir dasselbe Schema: beim Initialisieren einer Instanz einer SourcesViewModel
Klassenklasse in init
Wir erstellen ein "Abonnement", das während des gesamten "Lebenszyklus" der Klasseninstanz funktioniert, SourcesViewModel
und stellen sicher, dass die Liste der Informationsquellen vom sources
Land country
und der Suchzeichenfolge abhängt searchString
.Mit der Hilfe Combine
ziehen wir die Kette von den "Herausgebern" $searchString
und $country
geben "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 cancellableSet
mit dem Operator .store ( in: &self.cancellableSet)
.Nachdem wir nun View Model
unsere Informationsquellen zur Verfügung haben, können wir mit der Erstellung beginnen UI
. B SwiftUI
zu synchronisieren View
cObservableObject
Das Modell verwendet eine @ObservedObject
Variable, die auf eine Instanz der Klasse dieses Modells verweist.Fügen Sie der Struktur die ContentViewSources
Klasseninstanz SourcesViewModel
in Form einer Variablen hinzu var sourcesViewModel
, entfernen Sie sie Text ("Hello, World!")
und platzieren Sie Ihre eigene View
für jede der drei @Published
Eigenschaften sourcesViewModel
: - Textfeld
SearchView
fü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 SearchView
und das "Land" mit Picker
, und der Rest geschieht AUTOMATISCH.Die Liste der Informationsquellen SourcesList
enthält nur minimale Informationen zu jeder Quelle - den Namen source.name
und eine kurze Beschreibung source.description
:
... Sie können jedoch über den Link, NavigationLink
in dem destination
wir angeben, DetailSourceView
welche Quelldaten die Informationsquelle source
und 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, ArticlesViewModel
fü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 UIImage
für Artikel herunterladen Article
.
Das Modell des Artikels Article
enthält ein URL
dazugehöriges Bild urlToImage
:
Auf dieser Grundlage URL
mü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 ImageLoader
mithilfe 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
url
gleich nil
, kehre zurück Just(nil)
, - basierend auf der
url
Form des "Herausgebers" dataTaskPublisher(for:)
, dessen Ausgabewert Output
ein 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 data
und Form UIImage
, - Wenn bei den vorherigen Schritten ein Rückgabefehler auftritt
nil
, - Wir liefern das Ergebnis an den
main
Stream, da wir davon ausgehen, dass es im Design weiter verwendet UI
wird. - "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 ObservableObject
mit zwei @Published
Eigenschaften implementiert :@Published url: URL?
sind URL
Bilder@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 flatMap
und 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 wurdeAnyCancellable
store(in: &self.cancellableSet)
nil URL
image == nil
. Wenn während des Downloadvorgangs ein Fehler festgestellt wird, fehlt das Bild, dh es image
bleibt gleich nil
.In SwiftUI
zeigen 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 Rectangle
mit 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 nil
Sie 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 URL
Bild, aber der Zugriff darauf ist geschlossen, und wir erhalten ein Rechteck Rectangle
mit rotierendem Text Text("Loading...")
, das niemals ersetzt wird:
Wenn sich das URL
Bild in dieser Situation von dem Bild unterscheidet nil
, kann die Gleichheit des nil
Bildes image
bedeuten, 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 @Published
Eigenschaften 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 init
alle Fehler Error
, die beim Laden des Bildes auftreten, und akkumulieren deren Vorhandensein in der @Published
Eigenschaft 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 fetchImageErr
mit der Initialisierung des "Publishers" Future
, mit dem ein einzelner TYPE-Wert Result
mithilfe eines Abschlusses asynchron abgerufen werden kann . Der Abschluss hat einen Parameter - Promise
der eine Funktion von TYPE ist (Result<Output, Failure>) → Void
: Wir werden das
Ergebnis Future
in 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 url
auf nil
und noData
weiter true
: Wenn ja, geben Sie den Fehler zurück, wenn nicht, übertragen Sie ihn url
weiter durch Kette,1. Erstellen Sie einen "Herausgeber", dataTaskPublisher(for:)
dessen Eingabe - ist url
, und der Ausgabewert Output
ist 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.statusCode
im 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 data
zu UIImage
,4. liefern das Ergebnis an den main
Strom, da wir davon ausgehen , dass wir es später im Design verwenden UI
- wir „subscribe“ auf den empfangene „Verleger“ mit sink
seinen Schließungen receiveCompletion
und 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, ArticleImage
wo wir die neue @Published
Variable 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-key
oder 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 HTTP
Code 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 Never
kö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 Result
mithilfe eines Abschlusses asynchron einen einzelnen TYPE-Wert abzurufen. Der Abschluss hat einen Parameter - Promise
eine Funktion von TYPE (Result<Output, Failure>) -> Void
: Wir werden den
Empfang mit dem Operator "TYPE Erase" Future
in 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()
fetchArticlesErr
fetchArticles
, 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, URLError
wenn auf der Clientseite ein Fehler auftritt (Unfähigkeit, den Server zu kontaktieren, Verbot des Sicherheitssystems ATS
usw.).Wenn wir Fehler in anzeigen möchten, SwiftUI
benötigen wir den entsprechenden View Model
, den wir aufrufen werden ArticlesViewModelErr
:
In der Klasse ArticlesViewModelErr
, die das Protokoll implementiert ObservableObject
, haben wir diesmal VIER @Published
Eigenschaften:@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 $indexEndpoint
und $searchString
die Ausgabe „Verlag“ AnyPublisher<[Article],NewsError>
, auf die wir mit dem „Abonnent“ „unterzeichnet“ sink
und wir haben eine Menge von Artikeln erhalten articles
oder Fehler articlesError
.In unserer Klasse haben NewsAPI
wir 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
$searchString
endpoint
$indexEndpoint
$searchString
Combine
Publishers.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" sink
und verwenden seine Schließungen receiveCompletion
und receiveValue
Um vom „Verlag“ entweder den Wert einer Reihe von Artikeln articles
oder 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 abrufenarticles
oder 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 SwiftU
I 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>,
, url
der JSON
Informationen basierend auf dem Satz asynchron empfängt und direkt platziert im Codable
Modell 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 url
einen 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 HTTP
Anforderungen mit Hilfe Combine
seiner URLSession
„Herausgeber“ dataTaskPublisher
und Codable
. Wenn Sie keine Fehler nachverfolgen müssen, erhalten Sie einen sehr einfachen Generic
5-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 T
einfügt url
:
Dieser Code ist sehr einfach zu verwenden, um einen bestimmten Herausgeber abzurufen, wenn die Quelldaten Folgendes url
sind: Zum Beispiel Endpoint
erfordert 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 HTTP
Abfrageausführung mithilfe Combine
einen "Herausgeber" erstellen AnyPublisher<UIImage?, Never>
, der asynchron Daten auswählt und ein UIImage-Image empfängt? basierend auf URL
. Bild- ImageLoade
Downloader 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 NavigationLink
der Simulator mit einem Fehler arbeitet , wenn Sie den Simulator für die in diesem Artikel vorgestellte Anwendung verwenden . Sie können verwendenNavigationLink
auf 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 http
stattdessen https
noch "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» )