
Mittlerweile verwenden fast 100% der Anwendungen Netzwerke, sodass jeder mit der Organisation und Verwendung der Netzwerkschicht konfrontiert ist. Es gibt zwei Hauptansätze zur Lösung dieses Problems: Sie verwenden entweder Bibliotheken von Drittanbietern oder Ihre eigene Implementierung der Netzwerkschicht. In diesem Artikel werden wir die zweite Option betrachten und versuchen, eine Netzwerkschicht unter Verwendung der neuesten Funktionen der Sprache unter Verwendung von Protokollen und Aufzählungen zu implementieren. Dadurch wird das Projekt vor unnötigen Abhängigkeiten in Form zusätzlicher Bibliotheken geschützt. Diejenigen, die Moya jemals gesehen haben, werden sofort viele ähnliche Details bei der Implementierung und Verwendung erkennen, so wie es ist. Nur dieses Mal werden wir es selbst tun, ohne Moya und Alamofire zu berühren.
In diesem Handbuch erfahren Sie, wie Sie eine Netzwerkschicht auf reinem Swift implementieren, ohne Bibliotheken von Drittanbietern zu verwenden. Sobald Sie diesen Artikel gelesen haben, wird Ihr Code
- protokollorientiert
- einfach zu bedienen
- einfach zu bedienen
- Typ sicher
- Für Endpunkte werden Aufzählungen verwendet
Nachfolgend finden Sie ein Beispiel dafür, wie die Verwendung unserer Netzwerkschicht nach ihrer Implementierung aussehen wird:

Durch einfaches Schreiben von
router.request (. Und unter Verwendung aller Möglichkeiten von Aufzählungen werden alle möglichen Abfrageoptionen und ihre Parameter
angezeigt .
Zunächst ein wenig zur Struktur des ProjektsWann immer Sie etwas Neues schaffen und um in Zukunft alles leicht verstehen zu können, ist es sehr wichtig, alles richtig zu organisieren und zu strukturieren. Ich bin der Überzeugung, dass eine ordnungsgemäß organisierte Ordnerstruktur ein wichtiges Detail beim Erstellen der Anwendungsarchitektur ist. Damit wir alles richtig in Ordnern anordnen können, erstellen wir sie im Voraus. Dies sieht wie die allgemeine Ordnerstruktur im Projekt aus:
Endpointtype-ProtokollZunächst müssen wir unser
EndPointType- Protokoll definieren. Dieses Protokoll enthält alle erforderlichen Informationen zum Konfigurieren der Anforderung. Was ist eine Anfrage (Endpunkt)? Im Wesentlichen handelt es sich um eine URLRequest mit allen zugehörigen Komponenten wie Headern, Anforderungsparametern und Anforderungshauptteil. Das
EndPointType- Protokoll ist der wichtigste Teil unserer Implementierung auf Netzwerkebene. Erstellen wir eine Datei und nennen sie
EndPointType . Legen Sie diese Datei in den Dienstordner (nicht in den EndPoint-Ordner, warum - es wird etwas später klar sein).
HTTP-ProtokolleUnser
EndPointType enthält mehrere Protokolle, die wir zum Erstellen einer Anforderung benötigen. Mal sehen, was diese Protokolle sind.
HTTPMethodErstellen Sie eine Datei, nennen Sie sie
HTTPMethod und legen Sie sie im Ordner Service ab. Diese Auflistung wird verwendet, um die HTTP-Methode unserer Anfrage festzulegen.
HTTPTaskErstellen Sie eine Datei, nennen Sie sie
HTTPTask und legen Sie sie im
Dienstordner ab . HTTPTask ist für die Konfiguration der Parameter einer bestimmten Anforderung verantwortlich. Sie können so viele verschiedene Abfrageoptionen hinzufügen, wie Sie benötigen, aber ich werde im Gegenzug regelmäßige Abfragen, Abfragen mit Parametern, Abfragen mit Parametern und Überschriften durchführen, sodass ich nur diese drei Arten von Abfragen ausführen werde.

Im nächsten Abschnitt werden wir die
Parameter diskutieren und wie wir mit ihnen arbeiten werden
HTTPHeadersHTTPHeaders sind nur Typealias für ein Wörterbuch. Sie können es oben in Ihrer
HTTPTask- Datei erstellen.
public typealias HTTPHeaders = [String:String]
Parameter & KodierungErstellen Sie eine Datei, nennen Sie sie
ParameterEncoding und legen Sie sie im Ordner Encoding ab. Erstellen Sie Typealien für
Parameter , es wird wieder ein reguläres Wörterbuch sein. Wir tun dies, um den Code verständlicher und lesbarer zu machen.
public typealias Parameters = [String:Any]
Definieren Sie als Nächstes ein
ParameterEncoder- Protokoll mit einer einzelnen Codierungsfunktion. Die Codierungsmethode verfügt über zwei Parameter:
inout URLRequest und
Parameters .
INOUT ist ein Swift-Schlüsselwort, das einen Funktionsparameter als Referenz definiert. In der Regel werden Parameter als Werte an die Funktion übergeben. Wenn Sie in einem Aufruf vor einem Funktionsparameter
inout schreiben, definieren Sie diesen Parameter als Referenztyp. Um mehr über inout Argumente zu erfahren, können Sie diesem Link folgen. Kurz gesagt, mit
inout können Sie den Wert der Variablen selbst ändern, die an die Funktion übergeben wurde, und nicht nur ihren Wert im Parameter
abrufen und innerhalb der Funktion damit arbeiten. Das
ParameterEncoder- Protokoll wird in
JSONParameterEncoder und in
URLPameterEncoder implementiert .
public protocol ParameterEncoder { static func encode(urlRequest: inout URLRequest, with parameters: Parameters) throws }
ParameterEncoder enthält eine einzelne Funktion, deren Aufgabe es ist, Parameter zu codieren. Diese Methode kann einen Fehler auslösen, der behandelt werden muss, daher verwenden wir throw.
Es kann auch nützlich sein, keine Standardfehler, sondern benutzerdefinierte Fehler zu erzeugen. Es ist immer ziemlich schwierig zu entschlüsseln, was Xcode Ihnen bietet. Wenn Sie alle Fehler angepasst und beschrieben haben, wissen Sie immer genau, was passiert ist. Definieren wir dazu eine Aufzählung, die von
Error erbt.

Erstellen Sie eine Datei, nennen Sie sie
URLParameterEncoder und legen Sie sie im Ordner
Encoding ab .

Dieser Code nimmt eine Liste von Parametern auf, konvertiert und formatiert sie zur Verwendung als URL-Parameter. Wie Sie wissen, sind einige Zeichen in der URL nicht zulässig. Die Parameter werden auch durch das Symbol "&" getrennt, daher müssen wir uns darum kümmern. Wir müssen auch den Standardwert für die Header festlegen, wenn sie nicht in der Anforderung festgelegt sind.
Dies ist der Teil des Codes, der durch Unit-Tests abgedeckt werden soll. Das Erstellen einer URL-Anfrage ist der Schlüssel, da wir sonst viele unnötige Fehler provozieren können. Wenn Sie die offene API verwenden, möchten Sie offensichtlich nicht das gesamte mögliche Volumen an Anforderungen für fehlgeschlagene Tests verwenden. Wenn Sie mehr über Unit-Tests erfahren möchten, können Sie mit diesem Artikel beginnen.
JSONParameterEncoderErstellen Sie eine Datei, nennen Sie sie
JSONParameterEncoder und legen Sie sie im Ordner Encoding ab.

Alles ist das gleiche wie im Fall von
URLParameter , nur hier werden wir die Parameter für JSON konvertieren und die Parameter, die die Codierung "application / json" definieren, erneut zum Header hinzufügen.
NetzwerkrouterErstellen Sie eine Datei, nennen Sie sie
NetworkRouter und legen Sie sie im Dienstordner ab. Beginnen wir mit der Definition von Typealien für den Abschluss.
public typealias NetworkRouterCompletion = (_ data: Data?,_ response: URLResponse?,_ error: Error?)->()
Als nächstes definieren wir das
NetworkRouter- Protokoll.
NetworkRouter verfügt über einen
EndPoint , den es für Anforderungen verwendet. Sobald die Anforderung abgeschlossen ist, wird das Ergebnis dieser Anforderung an den
NetworkRouterCompletion- Abschluss übergeben. Das Protokoll verfügt außerdem über eine
Abbruchfunktion , mit der langfristige Lade- und Entladeanforderungen unterbrochen werden können. Wir haben hier auch den
zugehörigen Typ verwendet, da unser
Router alle Arten von
EndPointType unterstützen soll . Ohne den zugehörigen Typ müsste der Router einen bestimmten Typ haben, der
EndPointType implementiert. Wenn Sie mehr über den zugehörigen Typ erfahren möchten, können Sie
diesen Artikel lesen.
RouterErstellen Sie eine Datei, nennen Sie sie
Router und legen Sie sie im Ordner Service ab. Wir deklarieren eine private Variable vom Typ
URLSessionTask . Alle Arbeiten werden daran sein. Wir machen es privat, weil wir nicht möchten, dass jemand außerhalb es ändern kann.
AnfrageHier erstellen wir
URLSession mit
URLSession.shared . Dies ist der einfachste Weg zum Erstellen. Denken Sie jedoch daran, dass diese Methode nicht die einzige ist. Sie können komplexere
URLSession- Konfigurationen verwenden, die das Verhalten ändern können. Mehr dazu in
diesem Artikel .
Die Anforderung wird durch Aufrufen der Funktion
buildRequest erstellt. Der Funktionsaufruf wird in do-try-catch eingeschlossen, da die Codierungsfunktionen in
buildRequest möglicherweise Ausnahmen
auslösen .
Antwort ,
Daten und
Fehler werden vollständig übergeben.
Build-AnfrageWir erstellen unsere Anfrage mit der Funktion
buildRequest . Diese Funktion ist für alle wichtigen Arbeiten in unserer Netzwerkschicht verantwortlich. Konvertiert
EndPointType im Wesentlichen in
URLRequest . Sobald sich
EndPoint in eine Anfrage verwandelt, können wir diese an die
Sitzung weitergeben . Hier passiert viel, also schauen wir uns die Methoden an.
Lassen Sie uns zunächst die
buildRequest- Methode untersuchen:
1. Wir initialisieren die
URLRequest- Anforderungsvariable. Wir legen unsere Basis-URL darin fest und fügen den Pfad der spezifischen Anfrage hinzu, die dazu verwendet wird.
2.
Weisen Sie request.httpMethod die http-Methode aus unserem
EndPoint zu .
3. Wir erstellen einen Do-Try-Catch-Block, da unsere Encoder möglicherweise einen Fehler auslösen. Durch das Erstellen eines großen Do-Try-Catch-Blocks entfällt die Notwendigkeit, für jeden Versuch einen separaten Block zu erstellen.
4. Überprüfen
Sie in switch
route.task .
5. Je nach Art der Aufgabe rufen wir den entsprechenden Encoder auf.
Parameter konfigurierenErstellen Sie die Funktion
configureParameters im Router.

Diese Funktion ist für die Konvertierung unserer Abfrageparameter verantwortlich. Da unsere API die Verwendung von
bodyParameters in Form von JSON und
URLParameters voraussetzt, die in das URL-Format konvertiert wurden, übergeben wir einfach die entsprechenden Parameter an die entsprechenden Konvertierungsfunktionen, die wir am Anfang des Artikels beschrieben haben. Wenn Sie eine API verwenden, die verschiedene Arten von Codierungen enthält, würde ich in diesem Fall empfehlen,
HTTPTask mit einer zusätzlichen Aufzählung mit dem Codierungstyp hinzuzufügen. Diese Auflistung sollte alle möglichen Arten von Codierungen enthalten. Fügen Sie danach in configureParameters ein weiteres Argument mit dieser Aufzählung hinzu. Wechseln Sie je nach Wert mit switch und stellen Sie die gewünschte Codierung ein.
Zusätzliche Überschriften hinzufügenErstellen Sie die Funktion
addAdditionalHeaders im Router.

Fügen Sie der Anfrage einfach alle erforderlichen Header hinzu.
AbbrechenDie
Abbruchfunktion sieht ziemlich einfach aus:
AnwendungsbeispielVersuchen wir nun, unsere Netzwerkschicht anhand eines realen Beispiels zu verwenden. Wir werden uns mit
TheMovieDB verbinden , um Daten für unsere Anwendung zu erhalten.
MovieEndPointErstellen Sie eine
MovieEndPoint- Datei und legen Sie sie im EndPoint-Ordner ab. MovieEndPoint ist dasselbe wie
und TargetType in Moya. Hier implementieren wir stattdessen unseren eigenen EndPointType. Ein Artikel, der beschreibt, wie Moya für ein ähnliches Beispiel verwendet wird, finden Sie unter
diesem Link .
import Foundation enum NetworkEnvironment { case qa case production case staging } public enum MovieApi { case recommended(id:Int) case popular(page:Int) case newMovies(page:Int) case video(id:Int) } extension MovieApi: EndPointType { var environmentBaseURL : String { switch NetworkManager.environment { case .production: return "https:
MoviemodelUm das
MovieModel- und JSON-Datenmodell in das Modell zu analysieren, wird das Decodable-Protokoll verwendet. Legen Sie diese Datei im Ordner
Modell ab .
Hinweis : Für eine detailliertere Kenntnis der Protokolle Codable, Decodable und Encodable können Sie
meinen anderen Artikel lesen, in dem alle Funktionen der Arbeit mit ihnen ausführlich beschrieben werden.
import Foundation struct MovieApiResponse { let page: Int let numberOfResults: Int let numberOfPages: Int let movies: [Movie] } extension MovieApiResponse: Decodable { private enum MovieApiResponseCodingKeys: String, CodingKey { case page case numberOfResults = "total_results" case numberOfPages = "total_pages" case movies = "results" } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: MovieApiResponseCodingKeys.self) page = try container.decode(Int.self, forKey: .page) numberOfResults = try container.decode(Int.self, forKey: .numberOfResults) numberOfPages = try container.decode(Int.self, forKey: .numberOfPages) movies = try container.decode([Movie].self, forKey: .movies) } } struct Movie { let id: Int let posterPath: String let backdrop: String let title: String let releaseDate: String let rating: Double let overview: String } extension Movie: Decodable { enum MovieCodingKeys: String, CodingKey { case id case posterPath = "poster_path" case backdrop = "backdrop_path" case title case releaseDate = "release_date" case rating = "vote_average" case overview } init(from decoder: Decoder) throws { let movieContainer = try decoder.container(keyedBy: MovieCodingKeys.self) id = try movieContainer.decode(Int.self, forKey: .id) posterPath = try movieContainer.decode(String.self, forKey: .posterPath) backdrop = try movieContainer.decode(String.self, forKey: .backdrop) title = try movieContainer.decode(String.self, forKey: .title) releaseDate = try movieContainer.decode(String.self, forKey: .releaseDate) rating = try movieContainer.decode(Double.self, forKey: .rating) overview = try movieContainer.decode(String.self, forKey: .overview) } }
NetzwerkmanagerErstellen Sie eine
NetworkManager- Datei im Manager-Ordner. Derzeit enthält NetworkManager nur zwei statische Eigenschaften: einen API-Schlüssel und eine Aufzählung, die den Servertyp beschreibt, zu dem eine Verbindung hergestellt werden soll.
NetworkManager enthält auch einen
Router vom Typ
MovieApi .
NetzwerkantwortErstellen Sie die
NetworkResponse- Enumeration in NetworkManager.

Wir verwenden diese Aufzählung bei der Verarbeitung von Antworten auf Anfragen und zeigen die entsprechende Nachricht an.
ErgebnisErstellen Sie eine
Ergebnisaufzählung in NetworkManager.

Wir verwenden das
Ergebnis, um festzustellen, ob die Anfrage erfolgreich war oder nicht. Wenn nicht, geben wir eine Fehlermeldung mit dem Grund zurück.
Antwortverarbeitung anfordernErstellen Sie die Funktion
handleNetworkResponse . Diese Funktion verwendet ein Argument, z. B. eine
HTTP-Antwort, und gibt das Ergebnis zurück.

In dieser Funktion geben wir abhängig vom empfangenen statusCode von HTTPResponse eine Fehlermeldung oder ein Zeichen für eine erfolgreiche Anforderung zurück. In der Regel bedeutet ein Code im Bereich von 200 bis 299 Erfolg.
Netzwerkanfrage stellenWir haben also alles getan, um unsere Netzwerkschicht zu nutzen. Versuchen wir, eine Anfrage zu stellen.
Wir werden eine Liste neuer Filme anfordern. Erstellen Sie eine Funktion und nennen Sie sie
getNewMovies .

Gehen wir Schritt für Schritt vor:
1. Wir definieren die Methode
getNewMovies mit zwei Argumenten: der Paginierungsseitenzahl und dem Vervollständigungshandler, der ein optionales Array von
Filmmodellen zurückgibt, oder einen optionalen Fehler.
2. Rufen Sie den
Router an . Wir übergeben die Seitenzahl und den Prozessabschluss im Abschluss.
3.
URLSession gibt einen Fehler zurück, wenn kein Netzwerk vorhanden ist oder aus irgendeinem Grund keine Anfrage gestellt werden konnte. Bitte beachten Sie, dass dies kein API-Fehler ist. Solche Fehler treten auf dem Client auf und treten normalerweise aufgrund der schlechten Qualität der Internetverbindung auf.
4. Wir müssen unsere
Antwort in
HTTPURLResponse umwandeln , da wir auf die
statusCode- Eigenschaft zugreifen
müssen .
5. Deklarieren Sie das
Ergebnis und initialisieren Sie es mit der
handleNetworkResponse- Methode
6.
Erfolg bedeutet, dass die Anfrage erfolgreich war und wir die erwartete Antwort erhalten haben. Dann prüfen wir, ob die Daten mit der Antwort geliefert wurden, und wenn nicht, beenden wir die Methode einfach über return.
7. Wenn die Antwort Daten enthält, müssen die empfangenen Daten in das Modell analysiert werden. Danach übergeben wir das resultierende Array von Modellen vollständig.
8. Übergeben Sie den Fehler im Fehlerfall
vollständig .
So funktioniert unsere eigene Netzwerkschicht auf reinem Swift, ohne Abhängigkeiten in Form von Pods und Bibliotheken von Drittanbietern zu verwenden. Um eine Test-API-Anforderung zum Abrufen einer Liste von Filmen zu erstellen, erstellen Sie einen MainViewController mit der
NetworkManager- Eigenschaft und rufen Sie die
getNewMovies- Methode auf.
class MainViewController: UIViewController { var networkManager: NetworkManager! init(networkManager: NetworkManager) { super.init(nibName: nil, bundle: nil) self.networkManager = networkManager } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .green networkManager.getNewMovies(page: 1) { movies, error in if let error = error { print(error) } if let movies = movies { print(movies) } } } }
Kleiner BonusSie hatten Situationen in Xcode, in denen Sie nicht verstanden haben, welche Art von Platzhalter an einem bestimmten Ort verwendet wird? Schauen Sie sich zum Beispiel den Code an, den wir gerade für
Router geschrieben haben .

Wir haben die
NetworkRouterCompletion selbst festgelegt, aber selbst in diesem Fall kann man leicht vergessen, um welchen Typ es sich handelt und wie man ihn verwendet. Aber unser geliebter Xcode hat sich um alles gekümmert, und es reicht aus, nur auf den Platzhalter zu doppelklicken, und Xcode ersetzt den gewünschten Typ.
FazitJetzt haben wir eine Implementierung einer protokollorientierten Netzwerkschicht, die sehr einfach zu verwenden ist und die Sie jederzeit an Ihre Bedürfnisse anpassen können. Wir haben seine Funktionalität verstanden und wie alle Mechanismen funktionieren.
Sie finden den Quellcode in
diesem Repository .