Dieser Artikel ist eine Aktualisierung des Artikels
Abrufen von Remote-Daten in iOS , der im November 2015 mit Objective-C geschrieben wurde und daher moralisch veraltet ist. Jetzt wird der Code angezeigt, der in Swift 3 und iOS 10 umgeschrieben wurde (die neueste Version ist Swift 4.1 und iOS 11, aber mein Computer unterstützt sie nicht mehr).
Kurze Theorie
URL-Format
http://www.google.com/?q=Hello&safe=off
- http ist ein Protokoll, das bestimmt, nach welchem Standard eine Anfrage gestellt wird. Weitere Optionen: https, ftp, Datei
www.google.com
- Domainname- / - Verzeichnis, in dem sich die benötigten Ressourcen befinden.
- Nach dem Fragezeichen (?) Gehen Sie zu den Parametern q = Hello & safe = off. Sie bestehen aus Schlüssel-Wert-Paaren.
- Die Anforderung gibt auch eine Methode an, die angibt, wie der Server diese Anforderung verarbeiten soll. Standardmäßig ist dies eine GET-Methode.
Diese URL aus dem Beispiel kann wie folgt gelesen werden: Eine http-Anfrage mit der GET-Methode wird an google.com im Stammverzeichnis / mit zwei q-Parametern mit Hello und safe mit off gesendet.
http-Header
Der Browser konvertiert die URL-Zeichenfolge in den Header und den Text der Anforderung. Bei einer http-Anforderung ist der Text leer und der Header lautet wie folgt
GET /?q=Hello&safe=off HTTP/1.1 Host: google.com Content-Length: 133 // //
Serveranforderungsdiagramm
Zuerst wird eine Anfrage erstellt, dann wird eine Verbindung hergestellt, eine Anfrage gesendet und eine Antwort empfangen.
Sitzungsdelegierte
Alle UI-Operationen (die der Benutzeroberfläche zugeordnet sind) werden im Hauptthread ausgeführt. Sie können diesen Thread nicht einfach nehmen und stoppen, während eine ressourcenintensive Operation ausgeführt wird. Daher bestand eine der Lösungen für dieses Problem darin, Delegierte zu erstellen. Somit werden Operationen asynchron und der Hauptthread wird ohne Unterbrechung ausgeführt. Wenn der erforderliche Vorgang abgeschlossen ist, wird die entsprechende Delegatmethode aufgerufen. Die zweite Lösung besteht darin, einen neuen Ausführungsthread zu erstellen.
Wie im ursprünglichen Buch verwenden wir einen Delegaten, damit die Operationen klarer zwischen den Methoden aufgeteilt werden. Obwohl der Code durch Blöcke kompakter ist.
Beschreibung der Arten von Sitzungsdelegierten
Wir verwenden
NSURLSessionDownloadDelegate und implementieren dessen Methode
URLSession: downloadTask: didFinishDownloadingToURL:. Das heißt, wir laden die Daten mit einem Witz in einen temporären Speicher herunter, und wenn der Download abgeschlossen ist, rufen wir die Delegate-Methode zur Verarbeitung auf.
Übergang zum Hauptstrom
Das Laden von Daten in den temporären Speicher wird nicht im Hauptstrom ausgeführt. Um diese Daten jedoch zum Ändern der Benutzeroberfläche zu verwenden, werden wir zum Hauptstrom wechseln.
Runaway Closure (@escaping)
Da aufgrund der Implementierung des Codes der Abschluss, den wir an die Methode zum Laden von Daten aus der URL übergeben, die Methode selbst überlebt, muss Swift 3 ihn explizit als @escaping und self als nicht besessen kennzeichnen, damit der Self-Link diesen Abschluss nicht erfasst und festhält. Dies sind jedoch die Nuancen der Implementierung der Swift-Sprache selbst und nicht die Technologie zum Empfangen von Daten per API.
Weiterleitungen (Weiterleitungen)
In einigen Fällen treten Weiterleitungen auf. Wenn wir beispielsweise eine kurze URL haben, geht der Browser beim Eingeben in die Suchleiste des Browsers zuerst zum Server, wo diese kurze URL entschlüsselt und an uns gesendet wird, und dann mit dieser vollständigen URL zum Zielserver. Bei Bedarf können wir diese Weiterleitungen mit
NSURLSessionTaskDelegate steuern. Standardmäßig verarbeitet
NSURLSession jedoch alle Details.
Serialisierungsschema
Bei der Serialisierung werden Daten ohne Verlust von Inhalten von einem Speichertyp auf einen anderen übertragen. Beispielsweise werden Daten in binärer Form gespeichert, um weniger Speicherplatz zu beanspruchen. Wenn sie über ein Netzwerk gesendet werden, werden sie in ein universelles JSON-Format (JavaScript Object Notation) konvertiert, das wir bereits in unserer Programmierumgebung entschlüsseln und in Objekte übersetzen.
JSON-Beispiel:
{ "name": "Martin Conte Mac Donell", "age": 29, "username": "fz" }
Geschweifte Klammern kennzeichnen ein Wörterbuch, und Objekte in einem Wörterbuch werden durch Schlüssel-Wert-Paare dargestellt.
API (Application Programming Interface)
In unserem Fall wird die API durch die Adresse dargestellt, von der wir zufällige Witze und JSON-Antwortformate erhalten, die wir in Strukturen analysieren müssen, die für die Manipulation geeignet sind
http:
Beispiel icndb API:
{ "type": "success", "value": { "id": 201, "joke": "Chuck Norris was what Willis was talkin' about" } }
Jetzt übe
Das gesamte Projekt wird wie beim letzten Mal ohne Verwendung eines Storyboards in Code implementiert. Der gesamte Code wird in 3 Dateien geschrieben:
AppDelegate.swift ,
MainViewController.swift und
HTTPCommunication.swift . AppDelegate.swift enthält die allgemeine Konfiguration der Anwendung. HTTPCommunication.swift konfiguriert die Verbindung (Anfrage, Sitzung) und empfängt Daten. In MainViewController.swift werden diese Daten für die Ausgabe serialisiert und enthalten auch Benutzeroberflächencode.
Erstellen Sie ein leeres Projekt. Der Einfachheit halber schreiben wir eine Anwendung nur für das
iPhone . Wir löschen
ViewController.swift ,
Main.storyboard und in
Info.plist löschen
wir auch den Link zum Storyboard, nämlich die Zeile
Main Storyboard File Base Name - String - Main .
Standardmäßig blockiert
App Transport Security unter iOS das Herunterladen aus dem Internet über reguläres http (nicht https).
Daher nehmen wir Änderungen an
Info.plist vor , wie unten gezeigt. Öffnen Sie dazu
Info.plist als
Quellcode und fügen Sie den folgenden Code hinzu:
<key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <false/> <key>NSExceptionDomains</key> <dict> <key>api.icndb.com</key> <dict> <key>NSExceptionAllowsInsecureHTTPLoads</key> <true/> <key>NSIncludesSubdomains</key> <true/> </dict> </dict> </dict>
Standardmäßig verbieten wir willkürliche Downloads: Der Schlüssel NSAllowsArbitraryLoads ist falsch. Aber wir fügen ausnahmsweise unsere Domain mit Witzen und allen Subdomains hinzu: Schlüsselwerte NSExceptionDomains.
Jetzt
schreiben wir in
AppDelegate.swift die
Anwendung (_: didFinishLaunchingWithOptions :) wie folgt um:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { self.window = UIWindow(frame: UIScreen.main.bounds)
Erstellen Sie die Datei
HTTPCommunication.swift . Und wir schreiben den folgenden Code hinein.
import UIKit // NSObject, (conform) NSObjectProtocol, // URLSessionDownloadDelegate , // , . class HTTPCommunication: NSObject { // completionHandler - , // // . var completionHandler: ((Data) -> Void)! // retrieveURL(_: completionHandler:) // url func retrieveURL(_ url: URL, completionHandler: @escaping ((Data) -> Void)) { } } // , NSObject // (conforms) URLSessionDownloadDelegate, // // . extension HTTPCommunication: URLSessionDownloadDelegate { // // . func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { } }
Jetzt werden wir den Code dieser Funktionen schreiben.
Kopieren Sie den Code
retrieveURL (_ url :, CompletionHandler :) // , // @escaping. func retrieveURL(_ url: URL, completionHandler: @escaping ((Data) -> Void)) { self.completionHandler = completionHandler let request: URLRequest = URLRequest(url: url) let session: URLSession = URLSession(configuration: .default, delegate: self, delegateQueue: nil) let task: URLSessionDownloadTask = session.downloadTask(with: request) // , // . task.resume() }
Kopieren Sie den Code
func urlSession (_ session :, downloadTask :, didFinishDownloadingTo :) func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { do { // // . // , try, // do {} catch {} let data: Data = try Data(contentsOf: location) // completionHandler . // , // , // , . DispatchQueue.main.async(execute: { self.completionHandler(data) }) } catch { print("Can't get data from location.") } }
Wir erstellen die Datei
MainViewController.swift und kopieren den folgenden Code, der die erforderliche Schnittstelle erstellt:
import UIKit class MainViewController: UIViewController { lazy var jokeLabel: UILabel = { let label: UILabel = UILabel(frame: CGRect.zero) label.lineBreakMode = .byWordWrapping label.textAlignment = .center label.numberOfLines = 0 label.font = UIFont.systemFont(ofSize: 16) label.sizeToFit() self.view.addSubview(label) return label }() // . var jokeID: Int = 0 // ActivityView , // , . lazy var activityView: UIActivityIndicatorView = { let activityView: UIActivityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: .gray) activityView.hidesWhenStopped = true activityView.startAnimating() view.addSubview(activityView) return activityView }() lazy var stackView: UIStackView = { let mainStackView: UIStackView = UIStackView(arrangedSubviews: [self.jokeLabel]) // mainStackView.spacing = 50 mainStackView.axis = .vertical mainStackView.distribution = .fillEqually self.view.addSubview(mainStackView) return mainStackView }() override func viewDidLoad() { super.viewDidLoad() self.title = "Chuck Norris Jokes" // stackView activityView, // . // stackView // label. self.configConstraints() // (E.2) // // . self.retrieveRandomJokes() // (E.3) } func retrieveRandomJokes() { } } extension MainViewController { func configConstraints() { // autoresizingMask (constraints) // false, // self.stackView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.stackView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor), self.stackView.leadingAnchor.constraint(equalTo: self.view.layoutMarginsGuide.leadingAnchor), self.stackView.trailingAnchor.constraint(equalTo: self.view.layoutMarginsGuide.trailingAnchor) ]) self.activityView.translatesAutoresizingMaskIntoConstraints = false // (constraints) activityView, // label: X Y // label X Y. NSLayoutConstraint.activate([ self.activityView.centerXAnchor.constraint(equalTo: self.jokeLabel.centerXAnchor), self.activityView.centerYAnchor.constraint(equalTo: self.jokeLabel.centerYAnchor) ]) } }
Wir haben die Benutzeroberfläche herausgefunden, jetzt können Sie die Funktionalität ausfüllen.
Hier ist der Code
retrieveRandomJokes () func retrieveRandomJokes() { let http: HTTPCommunication = HTTPCommunication() // url , force unwrap // url , let url: URL = URL(string: "http://api.icndb.com/jokes/random")! http.retrieveURL(url) { // self , weak self [weak self] (data) -> Void in // json // , , . // json , // . guard let json = String(data: data, encoding: String.Encoding.utf8) else { return } // : JSON: { "type": "success", "value": // { "id": 391, "joke": "TNT was originally developed by Chuck // Norris to cure indigestion.", "categories": [] } } print("JSON: ", json) do { let jsonObjectAny: Any = try JSONSerialization.jsonObject(with: data, options: []) // , Any // , . guard let jsonObject = jsonObjectAny as? [String: Any], let value = jsonObject["value"] as? [String: Any], let id = value["id"] as? Int, let joke = value["joke"] as? String else { return } // , // . self.activityView.stopAnimating() self.jokeID = id self.jokeLabel.text = joke } catch { print("Can't serialize data.") } } }
Führen Sie nun die Anwendung aus und erhalten Sie das folgende Ergebnis.
Während wir auf einen Witz von der Seite warten.

Schließlich wird der Witz geladen und angezeigt.

Im nächsten Artikel werden wir uns den zweiten Teil der Anwendung ansehen, der schnell umgeschrieben wurde. So können Sie neue Witze erhalten, ohne das Programm neu zu starten, und für Witze stimmen.