Moderner Code zum Erstellen von HTTP-Anforderungen in Swift 5 mithilfe von Combine und deren Verwendung in SwiftUI. Teil 1



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 Artikeln

werde 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 Sie

nun, was wir  APIfür den NewsAPI.org- Dienst benötigenund 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  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 endpointund kein Fehler zurückgegeben wird - Never:

func fetchArticles(from endpoint: Endpoint) -> AnyPublisher<[Article], Never> {
        guard let url = endpoint.absoluteURL else {              // 0
                    return Just([Article]()).eraseToAnyPublisher()
        }
           return
            URLSession.shared.dataTaskPublisher(for:url)        // 1
            .map{$0.data}                                       // 2
            .decode(type: NewsResponse.self,                    // 3
                    decoder: APIConstants .jsonDecoder)
            .map{$0.articles}                                   // 4
            .replaceError(with: [])                             // 5
            .receive(on: RunLoop.main)                          // 6
            .eraseToAnyPublisher()                              // 7
    }

  • 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 {      // 0
                       return Just([Source]()).eraseToAnyPublisher()
           }
              return
               URLSession.shared.dataTaskPublisher(for:url)      // 1
               .map{$0.data}                                     // 2
               .decode(type: SourcesResponse.self,               // 3
                       decoder: APIConstants .jsonDecoder)
               .map{$0.sources}                                  // 4
               .replaceError(with: [])                           // 5
               .receive(on: RunLoop.main)                        // 6
               .eraseToAnyPublisher()                            // 7
    }

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:

//     URL
     func fetch<T: Decodable>(_ url: URL) -> AnyPublisher<T, Error> {
                   URLSession.shared.dataTaskPublisher(for: url)             // 1
                    .map { $0.data}                                          // 2
                    .decode(type: T.self, decoder: APIConstants.jsonDecoder) // 3
                    .receive(on: RunLoop.main)                               // 4
                    .eraseToAnyPublisher()                                   // 5
    }

Dies vereinfacht die beiden vorherigen Methoden:

//   
     func fetchArticles(from endpoint: Endpoint)
                                     -> AnyPublisher<[Article], Never> {
         guard let url = endpoint.absoluteURL else {
                     return Just([Article]()).eraseToAnyPublisher() // 0
         }
         return fetch(url)                                          // 1
             .map { (response: NewsResponse) -> [Article] in        // 2
                             return response.articles }
                .replaceError(with: [Article]())                    // 3
                .eraseToAnyPublisher()                              // 4
     }
    
    //    
    func fetchSources(for country: String)
                                       -> AnyPublisher<[Source], Never> {
        guard let url = Endpoint.sources(country: country).absoluteURL
            else {
                    return Just([Source]()).eraseToAnyPublisher() // 0
        }
        return fetch(url)                                         // 1
            .map { (response: SourcesResponse) -> [Source] in     // 2
                            response.sources }
               .replaceError(with: [Source]())                    // 3
               .eraseToAnyPublisher()                             // 4
    }

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: IntEndpoint ( «», 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, healthbusiness, 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:

  1. wenn urlgleich nil, kehre zurück Just(nil),
  2. basierend auf der urlForm des "Herausgebers" dataTaskPublisher(for:), dessen Ausgabewert Outputein Tupel (data: Data, response: URLResponse)und ein Fehler ist  FailureURLError,
  3. Wir nehmen nur Daten map {}aus dem Tupel (data: Data, response: URLResponse)zur weiteren Verarbeitung  dataund Form UIImage,
  4. Wenn bei den vorherigen Schritten ein Rückgabefehler auftritt nil,
  5. Wir liefern das Ergebnis an den mainStream, da wir davon ausgehen, dass es im Design weiter verwendet UIwird.
  6. "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:

  1. @Published var indexEndpoint: IntEndpoint ( «», View), 
  2. @Published var searchString: String — ,  Endpoint: «» , ( «», View), 
  3.  @Published var articles: [Article] - ( «»,  NewsAPI.org )
  4.   @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 {
    // News  API key url: https://newsapi.org
    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 entsprechen

Diese 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 .

PS

1. 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 type
Combine: 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» )

All Articles