Alles was Sie brauchen ist URL

Bild

VKontakte-Benutzer tauschen täglich 10 Milliarden Nachrichten aus. Sie senden sich gegenseitig Fotos, Comics, Memes und andere Anhänge. Wir werden Ihnen URLProtocol , wie wir eine iOS-Anwendung zum Hochladen von Bildern mit URLProtocol , und Schritt für Schritt werden wir herausfinden, wie wir unsere eigene implementieren können.

Vor ungefähr anderthalb Jahren war die Entwicklung eines neuen Nachrichtenabschnitts in der VK-Anwendung für iOS in vollem Gange. Dies ist der erste Abschnitt, der vollständig in Swift geschrieben wurde. Es befindet sich in einem separaten Modul vkm (VK Messages), das nichts über das Gerät der Hauptanwendung weiß. Es kann sogar in einem separaten Projekt ausgeführt werden - die grundlegenden Funktionen zum Lesen und Senden von Nachrichten funktionieren weiterhin. In der Hauptanwendung werden Nachrichtencontroller über den entsprechenden Container View Controller hinzugefügt, um beispielsweise eine Liste von Konversationen oder Nachrichten in einer Konversation anzuzeigen.

Nachrichten sind einer der beliebtesten Bereiche der mobilen VKontakte-Anwendung. Daher ist es wichtig, dass sie wie eine Uhr funktionieren. Im messages kämpfen wir um jede Codezeile. Es hat uns immer sehr gut gefallen, wie ordentlich die Nachrichten in die Anwendung integriert sind, und wir bemühen uns sicherzustellen, dass alles beim Alten bleibt.

Nachdem wir den Abschnitt schrittweise mit neuen Funktionen gefüllt hatten, näherten wir uns der folgenden Aufgabe: Es war erforderlich, das Foto, das an die Nachricht angehängt ist, zuerst in einem Entwurf anzuzeigen und nach dem Senden in der allgemeinen Liste der Nachrichten zu erstellen. Wir könnten einfach ein Modul hinzufügen, um mit PHImageManager zu arbeiten, aber zusätzliche Bedingungen erschwerten die Aufgabe.

Bild


Bei der Auswahl eines Snapshots kann der Benutzer diesen verarbeiten: Filter anwenden, drehen, zuschneiden usw. In der VK-Anwendung wird diese Funktionalität in einer separaten AssetService Komponente AssetService . Jetzt musste man aus dem Nachrichtenprojekt lernen, mit ihm zu arbeiten.

Nun, die Aufgabe ist ganz einfach, wir werden es tun. Dies ist ungefähr die durchschnittliche Lösung, da es viele Variationen gibt. Wir nehmen das Protokoll, legen es in Nachrichten ab und füllen es mit Methoden. Wir erweitern den AssetService, passen das Protokoll an und fügen unsere Cache-Implementierung hinzu! für die Viskosität. Dann fügen wir die Implementierung in Nachrichten ein, fügen sie einem Dienst oder Manager hinzu, der mit all dem funktioniert, und beginnen, sie zu verwenden. Gleichzeitig kommt immer noch ein neuer Entwickler und verurteilt, während er versucht, alles herauszufinden, flüsternd ... (na ja, verstehen Sie). Gleichzeitig tritt Schweiß auf seiner Stirn auf.

Bild


Diese Entscheidung gefiel uns nicht . Es werden neue Entitäten AssetService , über die Nachrichtenkomponenten bei der Arbeit mit Bildern aus AssetService Bescheid wissen AssetService . Der Entwickler muss außerdem zusätzliche Arbeit leisten, um herauszufinden, wie dieses System funktioniert. Schließlich gab es eine zusätzliche implizite Verknüpfung zu den Komponenten des Hauptprojekts, die wir zu vermeiden versuchen, damit der Nachrichtenabschnitt weiterhin als unabhängiges Modul funktioniert.

Ich wollte das Problem lösen, damit das Projekt überhaupt nichts darüber wusste, welche Art von Bild ausgewählt wurde, wie es gespeichert werden sollte, ob es speziell geladen und gerendert werden musste. Darüber hinaus haben wir bereits die Möglichkeit, herkömmliche Bilder aus dem Internet herunterzuladen, nur werden sie nicht über einen zusätzlichen Dienst heruntergeladen, sondern einfach per URL . Tatsächlich gibt es keinen Unterschied zwischen den beiden Bildtypen. Nur einige werden lokal gespeichert, während andere auf dem Server gespeichert werden.

Wir hatten also eine sehr einfache Idee: Was ist, wenn das Laden lokaler Assets auch über eine URL erlernt werden URL ? Es scheint, als würde dies mit einem Klick auf Thanos ' Finger alle unsere Probleme lösen: Sie müssen nichts über AssetService wissen, neue Datentypen hinzufügen und die Entropie vergeblich erhöhen, lernen, einen neuen Bildtyp zu laden, sich um das Zwischenspeichern von Daten kümmern. Klingt nach einem Plan.

Wir brauchen nur eine URL


Wir haben diese Idee berücksichtigt und beschlossen, das URL Format zu definieren, das zum Laden lokaler Assets verwendet wird:

 asset://?id=123&width=1920&height=1280 

Wir werden den Wert der Eigenschaft localIdentifier von localIdentifier als PHObject und die Parameter width und height , um die Bilder der gewünschten Größe zu laden. Wir fügen auch einige weitere Parameter wie crop , filter und rotate , mit denen Sie mit den Informationen des verarbeiteten Bildes arbeiten können.

Um diese URL erstellen wir ein AssetURLProtocol :

 class AssetURLProtocol: URLProtocol { } 

Seine Aufgabe besteht darin, das Image über AssetService zu laden und die bereits gebrauchsfertigen Daten zurückzugeben.

All dies ermöglicht es uns, die Arbeit des URL Protokolls und des URL Loading System fast vollständig zu delegieren.

Innerhalb der Nachrichten kann mit den gängigsten URL nur in einem anderen Format gearbeitet werden. Es wird auch möglich sein, den vorhandenen Mechanismus zum Laden von Bildern wiederzuverwenden. Es ist sehr einfach, in der Datenbank zu serialisieren und das Zwischenspeichern von Daten über Standard- URLCache .

Hat es geklappt? Wenn Sie beim Lesen dieses Artikels ein Foto aus der Galerie an die Nachricht in der VKontakte-Anwendung anhängen können, dann ja :)

Bild

Um zu verdeutlichen, wie Sie Ihr URLProtocol implementieren, schlage ich vor, dies URLProtocol eines Beispiels zu betrachten.

Wir haben uns die Aufgabe gestellt: eine einfache Anwendung mit einer Liste zu implementieren, in der Sie eine Liste von Kartenschnappschüssen an den angegebenen Koordinaten anzeigen müssen. Zum Herunterladen von Snapshots verwenden wir den Standard- MKMapSnapshotter von MapKit und laden Daten über das benutzerdefinierte URLProtocol . Das Ergebnis könnte ungefähr so ​​aussehen:

Bild

Zunächst implementieren wir den Mechanismus zum Laden von Daten per URL . Um den Kartenschnappschuss anzuzeigen, müssen wir die Koordinaten des Punktes kennen - seine Breite und Länge ( latitude , longitude ). Definieren Sie das benutzerdefinierte URL Format, mit dem Informationen geladen werden sollen:

 map://?latitude=59.935634&longitude=30.325935 

Jetzt implementieren wir URLProtocol , das solche Links verarbeitet und das gewünschte Ergebnis generiert. Erstellen wir die MapURLProtocol Klasse, die wir von der Basisklasse URLProtocol erben. Trotz seines Namens ist URLProtocol eine abstrakte Klasse. URLProtocol Sie nicht verlegen, hier verwenden wir andere Konzepte - URLProtocol repräsentiert genau das URL Protokoll und hat keine Beziehung zu OOP-Begriffen. Also MapURLProtocol :

 class MapURLProtocol: URLProtocol { } 

Jetzt definieren wir einige erforderliche Methoden neu, ohne die das URL Protokoll nicht funktioniert:

1. canInit(with:)


 override class func canInit(with request: URLRequest) -> Bool { return request.url?.scheme == "map" } 

Die Methode canInit(with:) wird benötigt, um anzugeben, welche Arten von Anforderungen unser URL Protokoll verarbeiten kann. Angenommen, das Protokoll verarbeitet in diesem Beispiel nur Anforderungen mit einem map in der URL . Vor dem Starten einer Anforderung durchläuft die URL Loading System alle für die Sitzung registrierten Protokolle und ruft diese Methode auf. Das erste registrierte Protokoll, das bei dieser Methode true zurückgibt, wird zur Verarbeitung der Anforderung verwendet.

canonicalRequest(for:)


 override class func canonicalRequest(for request: URLRequest) -> URLRequest { return request } 

Die Methode canonicalRequest(for:) soll die Anforderung auf kanonische Form reduzieren. Die Dokumentation besagt, dass die Implementierung des Protokolls selbst entscheidet, was als Definition dieses Konzepts zu betrachten ist. Hier können Sie das Schema normalisieren, der Anforderung bei Bedarf Header hinzufügen usw. Die einzige Voraussetzung für die Funktion dieser Methode ist, dass für jede eingehende Anforderung immer das gleiche Ergebnis erzielt wird, auch weil diese Methode auch zur Suche nach zwischengespeicherten Antworten verwendet wird Anfragen in URLCache .

3. startLoading()


Die Methode startLoading() beschreibt die gesamte Logik zum Laden der erforderlichen Daten. In diesem Beispiel müssen Sie die Anforderungs- URL analysieren und basierend auf den Werten der latitude und latitude zu MKMapSnapshotter und den gewünschten Kartenschnappschuss laden.

 override func startLoading() { guard let url = request.url, let components = URLComponents(url: url, resolvingAgainstBaseURL: false), let queryItems = components.queryItems else { fail(with: .badURL) return } load(with: queryItems) } func load(with queryItems: [URLQueryItem]) { let snapshotter = MKMapSnapshotter(queryItems: queryItems) snapshotter.start( with: DispatchQueue.global(qos: .background), completionHandler: handle ) } func handle(snapshot: MKMapSnapshotter.Snapshot?, error: Error?) { if let snapshot = snapshot, let data = snapshot.image.jpegData(compressionQuality: 1) { complete(with: data) } else if let error = error { fail(with: error) } } 

Nach dem Empfang der Daten muss das Protokoll korrekt heruntergefahren werden:

 func complete(with data: Data) { guard let url = request.url, let client = client else { return } let response = URLResponse( url: url, mimeType: "image/jpeg", expectedContentLength: data.count, textEncodingName: nil ) client.urlProtocol(self, didReceive: response, cacheStoragePolicy: .allowed) client.urlProtocol(self, didLoad: data) client.urlProtocolDidFinishLoading(self) } 

Erstellen Sie zunächst ein Objekt vom Typ URLResponse . Dieses Objekt enthält wichtige Metadaten für die Beantwortung einer Anfrage. Dann führen wir drei wichtige Methoden für ein Objekt vom Typ URLProtocolClient . Die client Eigenschaft dieses Typs enthält jede Entität des URL Protokolls. Es fungiert als Proxy zwischen dem URL Protokoll und der gesamten URL Loading System , das beim URL Loading System dieser Methoden Rückschlüsse darauf zieht, was mit den Daten zu tun ist: Cache, Anforderungen an completionHandler , Protokollabschaltung irgendwie verarbeiten usw. und die Anzahl der Aufrufe dieser Methoden kann abhängig von der Protokollimplementierung variieren. Beispielsweise können wir Daten URLProtocolClient aus dem Netzwerk herunterladen und URLProtocolClient regelmäßig darüber informieren, um den Fortschritt des URLProtocolClient in der Schnittstelle URLProtocolClient .

Wenn beim Betrieb des Protokolls ein Fehler auftritt, muss URLProtocolClient auch korrekt verarbeitet und benachrichtigt werden:

 func fail(with error: Error) { client?.urlProtocol(self, didFailWithError: error) } 

Dieser Fehler wird dann an den completionHandler der Anfrage gesendet, wo er verarbeitet und dem Benutzer eine schöne Nachricht angezeigt werden kann.

4. stopLoading()


Die stopLoading() -Methode wird aufgerufen, wenn die Protokolloperation aus irgendeinem Grund abgeschlossen wurde. Dies kann entweder ein erfolgreicher Abschluss oder ein Fehlerabschluss oder eine Stornierung der Anforderung sein. Dies ist ein guter Ort, um belegte Ressourcen freizugeben oder temporäre Daten zu löschen.

 override func stopLoading() { } 

Damit ist die Implementierung des URL Protokolls abgeschlossen, das überall in der Anwendung verwendet werden kann. Fügen Sie noch ein paar Dinge hinzu, um unser Protokoll anzuwenden.

URLImageView


 class URLImageView: UIImageView { var task: URLSessionDataTask? var taskId: Int? func render(url: URL) { assert(task == nil || task?.taskIdentifier != taskId) let request = URLRequest(url: url) task = session.dataTask(with: request, completionHandler: complete) taskId = task?.taskIdentifier task?.resume() } private func complete(data: Data?, response: URLResponse?, error: Error?) { if self.taskId == task?.taskIdentifier, let data = data, let image = UIImage(data: data) { didLoadRemote(image: image) } } func didLoadRemote(image: UIImage) { DispatchQueue.main.async { self.image = image } } func prepareForReuse() { task?.cancel() taskId = nil image = nil } } 

Dies ist eine einfache Klasse, der Nachkomme von UIImageView , eine ähnliche Implementierung, die Sie wahrscheinlich in jeder Anwendung haben. Hier laden wir das Bild einfach über die URL in die Methode render(url:) und schreiben es in die image . Der Vorteil ist, dass Sie absolut jedes Bild hochladen können, entweder über die http / https URL oder über unsere benutzerdefinierte URL .

Um Anforderungen zum Laden von Bildern auszuführen, benötigen Sie außerdem ein Objekt vom Typ URLSession :

 let config: URLSessionConfiguration = { let c = URLSessionConfiguration.ephemeral c.protocolClasses = [ MapURLProtocol.self ] return c }() let session = URLSession( configuration: config, delegate: nil, delegateQueue: nil ) 

Die Sitzungskonfiguration ist hier besonders wichtig. In URLSessionConfiguration gibt es eine wichtige Eigenschaft für uns - protocolClasses . Dies ist eine Liste der Arten von URL Protokollen, die eine Sitzung mit dieser Konfiguration verarbeiten kann. Standardmäßig unterstützt die Sitzung die Verarbeitung von http / https Protokollen. Wenn benutzerdefinierte Unterstützung erforderlich ist, müssen diese angegeben werden. MapURLProtocol Sie in unserem Beispiel MapURLProtocol .

Sie müssen lediglich den View Controller implementieren, der Kartenschnappschüsse anzeigt. Den Quellcode finden Sie hier .

Hier ist das Ergebnis:

Bild

Was ist mit Caching?


Alles scheint gut zu funktionieren - bis auf einen wichtigen Punkt: Wenn wir die Liste hin und her scrollen, erscheinen weiße Flecken auf dem Bildschirm. Es scheint, dass Snapshots in keiner Weise zwischengespeichert werden und für jeden Aufruf der render(url:) -Methode MKMapSnapshotter wir MKMapSnapshotter Daten über MKMapSnapshotter . Dies braucht Zeit und daher solche Ladelücken. Es lohnt sich, einen Daten-Caching-Mechanismus zu implementieren, damit bereits erstellte Snapshots nicht erneut heruntergeladen werden. Hier nutzen wir die Leistung des URL Loading System URLCache , für das bereits ein Caching-Mechanismus für URLCache vorgesehen ist.

Betrachten Sie diesen Prozess genauer und teilen Sie die Arbeit mit dem Cache in zwei wichtige Phasen ein: Lesen und Schreiben.

Lesen


Um zwischengespeicherte Daten korrekt zu lesen, benötigt die URL Loading System Hilfe, um Antworten auf mehrere wichtige Fragen zu erhalten:

1. Welcher URLCache soll verwendet werden?

Natürlich ist URLCache.shared bereits fertig, aber die URL Loading System URLCache.shared kann es nicht immer verwenden. Schließlich möchte der Entwickler möglicherweise seine eigene URLCache Entität erstellen und verwenden. Um diese Frage zu beantworten, verfügt die URLSessionConfiguration Sitzungskonfiguration über eine urlCache Eigenschaft. Es wird sowohl zum Lesen als auch zum Aufzeichnen von Antworten auf Anforderungen verwendet. Wir werden einige URLCache für diese Zwecke in unserer vorhandenen Konfiguration URLCache .

 let config: URLSessionConfiguration = { let c = URLSessionConfiguration.ephemeral c.urlCache = ImageURLCache.current c.protocolClasses = [ MapURLProtocol.self ] return c }() 

2. Muss ich zwischengespeicherte Daten verwenden oder erneut herunterladen?

Die Antwort auf diese Frage hängt von der URLRequest Anforderung ab, die wir ausführen werden. Beim Erstellen einer Anforderung haben wir die Möglichkeit, zusätzlich zur URL eine Cache-Richtlinie im Argument cachePolicy anzugeben.

 let request = URLRequest( url: url, cachePolicy: .returnCacheDataElseLoad, timeoutInterval: 30 ) 

Der Standardwert ist .useProtocolCachePolicy , der ebenfalls in der Dokumentation beschrieben wird. Dies bedeutet, dass in dieser Version die Aufgabe, eine zwischengespeicherte Antwort auf eine Anforderung zu finden und ihre Relevanz zu bestimmen, vollständig in der Implementierung des URL Protokolls liegt. Aber es gibt einen einfacheren Weg. Wenn Sie den Wert .returnCacheDataElseLoad , .returnCacheDataElseLoad das URLProtocol URL Loading System beim Erstellen der nächsten Entität URLProtocol Teil der Arbeit: Es fragt urlCache zwischengespeicherten Antwort auf die aktuelle Anforderung mithilfe der Methode cachedResponse(for:) . Wenn zwischengespeicherte Daten vorhanden sind, wird ein Objekt vom Typ CachedURLResponse sofort übertragen, wenn das URLProtocol initialisiert und in der Eigenschaft cachedResponse gespeichert wird:

 override init( request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) { super.init( request: request, cachedResponse: cachedResponse, client: client ) } 

CachedURLResponse ist eine einfache Klasse, die Daten ( Data ) und Metainformationen für sie ( URLResponse ) enthält.

Wir können die startLoading Methode nur ein startLoading ändern und den Wert dieser Eigenschaft darin überprüfen - und das Protokoll sofort mit diesen Daten beenden:

 override func startLoading() { if let cachedResponse = cachedResponse { complete(with: cachedResponse.data) } else { guard let url = request.url, let components = URLComponents(url: url, resolvingAgainstBaseURL: false), let queryItems = components.queryItems else { fail(with: .badURL) return } load(with: queryItems) } } 

Aufzeichnen


Um Daten im Cache zu finden, müssen Sie sie dort ablegen. Das URL Loading System kümmert sich ebenfalls um diese Arbeit. Alles, was von uns verlangt wird, ist ihr mitzuteilen, dass wir die Daten zwischenspeichern möchten, wenn das Protokoll mithilfe der cacheStoragePolicy Cache-Richtlinieneinstellung cacheStoragePolicy . Dies ist eine einfache Aufzählung mit den folgenden Werten:

 enum StoragePolicy { case allowed case allowedInMemoryOnly case notAllowed } 

Sie bedeuten, dass das Zwischenspeichern im Speicher und auf der Festplatte nur im Speicher zulässig oder verboten ist. In unserem Beispiel geben wir an, dass das Zwischenspeichern im Speicher und auf der Festplatte zulässig ist. Warum nicht?

 client.urlProtocol(self, didReceive: response, cacheStoragePolicy: .allowed) 

Mit ein paar einfachen Schritten haben wir die Möglichkeit unterstützt, Karten-Snapshots zwischenzuspeichern. Und jetzt funktioniert die Anwendung so:

Bild

Wie Sie sehen, gibt es keine weißen Flecken mehr - die Karten werden einmal geladen und dann einfach aus dem Cache wiederverwendet.

Nicht immer einfach


Bei der Implementierung des URL Protokolls sind eine Reihe von Abstürzen aufgetreten.

Die erste URLCache die interne Implementierung der Interaktion des URL Loading System URLCache mit URLCache beim Zwischenspeichern von Antworten auf Anforderungen. In der Dokumentation heißt es : Trotz der URLCache von URLCache kann der Betrieb der cachedResponse(for:) und storeCachedResponse(_:for:) zum Lesen / Schreiben von Antworten auf Anforderungen zu einer URLCache von Zuständen führen. Daher sollte dieser Punkt in Unterklassen von URLCache berücksichtigt werden. Wir haben erwartet, dass mit URLCache.shared dieses Problem gelöst wird, aber es hat sich als falsch herausgestellt. Um dies zu beheben, verwenden wir einen separaten ImageURLCache Cache, einen Nachkommen von URLCache , in dem wir die angegebenen Methoden synchron in einer separaten Warteschlange ausführen. Als angenehmen Bonus können wir die Cache-Kapazität im Speicher und auf der Festplatte separat von anderen URLCache Entitäten URLCache .

 private static let accessQueue = DispatchQueue( label: "image-urlcache-access" ) override func cachedResponse(for request: URLRequest) -> CachedURLResponse? { return ImageURLCache.accessQueue.sync { return super.cachedResponse(for: request) } } override func storeCachedResponse(_ response: CachedURLResponse, for request: URLRequest) { ImageURLCache.accessQueue.sync { super.storeCachedResponse(response, for: request) } } 

Ein weiteres Problem wurde nur auf Geräten mit iOS 9 reproduziert. Die Methoden zum Starten und Beenden des Ladens des URL Protokolls können für verschiedene Threads ausgeführt werden, was zu seltenen, aber unangenehmen Abstürzen führen kann. Um das Problem zu lösen, speichern wir den aktuellen Thread in der startLoading Methode und führen dann den Download-Abschlusscode direkt in diesem Thread aus.

 var thread: Thread! override func startLoading() { guard let url = request.url, let components = URLComponents(url: url, resolvingAgainstBaseURL: false), let queryItems = components.queryItems else { fail(with: .badURL) return } thread = Thread.current if let cachedResponse = cachedResponse { complete(with: cachedResponse) } else { load(request: request, url: url, queryItems: queryItems) } } 

 func handle(snapshot: MKMapSnapshotter.Snapshot?, error: Error?) { thread.execute { if let snapshot = snapshot, let data = snapshot.image.jpegData(compressionQuality: 0.7) { self.complete(with: data) } else if let error = error { self.fail(with: error) } } } 

Wann kann ein URL-Protokoll nützlich sein?


Infolgedessen stößt fast jeder Benutzer unserer iOS-Anwendung auf die eine oder andere Weise auf Elemente, die über das URL Protokoll funktionieren. Neben dem Herunterladen von Medien aus der Galerie helfen uns verschiedene Implementierungen von URL Protokollen beim Anzeigen von Karten und Umfragen sowie beim Anzeigen von Chat-Avataren, die aus Fotos ihrer Teilnehmer bestehen.

Bild

Bild

Bild

Bild

Wie jede Lösung hat URLProtocol seine Vor- und Nachteile.

Nachteile von URLProtocol


  • Fehlende strikte Eingabe - Beim Erstellen einer URL Schema- und Linkparameter manuell über Zeichenfolgen angegeben. Wenn Sie einen Tippfehler machen, wird der gewünschte Parameter nicht verarbeitet. Dies kann das Debuggen der Anwendung und die Suche nach Fehlern in ihrem Betrieb erschweren. In der VKontakte-Anwendung verwenden wir spezielle URLBuilder , die die endgültige URL basierend auf den übergebenen Parametern bilden. Diese Entscheidung ist nicht sehr schön und widerspricht etwas dem Ziel, keine zusätzlichen Einheiten zu produzieren, aber es gibt noch keine bessere Idee. Wir wissen jedoch, dass es einen speziellen URLBuilder , der Ihnen hilft, keinen Fehler zu machen, wenn Sie eine benutzerdefinierte URL erstellen müssen.
  • Nicht offensichtliche Abstürze - Ich habe bereits einige Szenarien beschrieben, die dazu führen können, dass eine Anwendung mit URLProtocol abstürzt. Vielleicht gibt es noch andere. Solche Probleme werden jedoch wie üblich entweder durch sorgfältiges Lesen der Dokumentation oder durch eingehende Untersuchung der Stapelverfolgung und Auffinden der Wurzel des Problems gelöst.

Vorteile von URLProtocol


  • Schwache Komponentenkonnektivität - Der Teil der Anwendung, der das Laden der benötigten Daten initiiert, weiß möglicherweise überhaupt nicht, wie sie organisiert ist: Welche Komponenten werden dafür verwendet, wie ist das Caching angeordnet? Wir kennen nur ein bestimmtes Format URL- und interagieren nur damit.
  • URLEinfache Implementierung - Für den korrekten Betrieb des Protokolls reicht es aus, mehrere einfache Methoden zu implementieren und das Protokoll zu registrieren. Danach kann es überall in der Anwendung verwendet werden.
  • — , , URL -. URL , URLSession , URLSessionDataTask .
  • URL - URL -, URL Loading System .
  • * API — . , API, - , URL -. , API , . URL - http / https .

URL - — . . - , - , , , — , . , , — URL .

GitHub

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


All Articles