Interaction avec le serveur via l'API dans iOS sur Swift 3. Partie 1

Cet article est une mise à jour de l'article Obtenir des données distantes dans iOS , écrit en novembre 2015 en utilisant Objective-C et donc moralement obsolète. Maintenant, le code sera donné qui a été réécrit sur Swift 3 et iOS 10 (la dernière version est Swift 4.1 et iOS 11, mais mon ordinateur ne les prend plus en charge).

Brève théorie


Format d'URL


http://www.google.com/?q=Hello&safe=off 

  • http est un protocole qui détermine par quelle norme une demande est faite. Plus d'options: https, ftp, fichier
  • www.google.com - nom de domaine
  • / - répertoire où se trouvent les ressources dont nous avons besoin.
  • Après le point d'interrogation (?) Allez dans les paramètres q = Bonjour & sûr = off. Ils sont constitués de paires clé-valeur.
  • La demande indique également une méthode qui indique comment le serveur doit gérer cette demande. Par défaut, il s'agit d'une méthode GET.

Cette URL de l'exemple peut être lue comme suit: une requête http avec la méthode GET est envoyée à google.com, dans le répertoire racine /, avec deux paramètres q avec Hello et sûr avec off.

en-tête http


Le navigateur convertit la chaîne d'URL en en-tête et corps de la demande. Pour une requête http, le corps est vide et l'en-tête est le suivant

 GET /?q=Hello&safe=off HTTP/1.1 Host: google.com Content-Length: 133 //    //     

Diagramme de demande du serveur


Tout d'abord, une demande est créée, puis une connexion est établie, une demande est envoyée et une réponse est reçue.

Délégués de session


Toutes les opérations d'interface utilisateur (associées à l'interface utilisateur) sont effectuées dans le thread principal. Vous ne pouvez pas simplement prendre et arrêter ce fil pendant qu'une sorte d'opération gourmande en ressources est en cours. Par conséquent, l'une des solutions à ce problème était de créer des délégués. Ainsi, les opérations deviennent asynchrones et le thread principal s'exécute sans interruption. Une fois l'opération nécessaire terminée, la méthode déléguée correspondante sera appelée. La deuxième solution est de créer un nouveau thread d'exécution.

Comme dans le livre d'origine, nous utilisons un délégué pour que les opérations soient plus clairement réparties entre les méthodes. Bien que le code soit plus compact à travers des blocs.

Description des types de délégués de session


Nous utilisons NSURLSessionDownloadDelegate et implémentons sa méthode URLSession: downloadTask: didFinishDownloadingToURL :. Autrement dit, nous téléchargeons les données avec une blague dans un stockage temporaire, et lorsque le téléchargement est terminé, nous appelons la méthode déléguée pour le traitement.

Transition vers le flux principal


Le chargement des données dans le stockage temporaire n'est pas effectué dans le flux principal, mais afin d'utiliser ces données pour modifier l'interface utilisateur, nous irons dans le flux principal.

Fermeture de fugue (@escaping)


Étant donné que, en raison de la mise en œuvre du code, la fermeture que nous transmettons à la méthode de chargement des données à partir de l'URL survivra à la méthode elle-même, pour Swift 3, il est nécessaire de la désigner explicitement @escaping, et de la rendre sans propriétaire afin que le lien automatique ne capture pas et ne se maintienne pas dans cette fermeture. Mais ce sont les nuances de la mise en œuvre du langage Swift lui-même, et non la technologie de réception de données par API.

Redirections (redirections)


Dans certains cas, des redirections se produisent. Par exemple, si nous avons une URL courte, puis lorsque nous la saisissons dans la barre de recherche du navigateur, le navigateur va d'abord au serveur, où cette URL courte est déchiffrée et nous est envoyée, puis nous allons au serveur cible en utilisant cette URL complète. Si nécessaire, nous pouvons contrôler ces redirections à l'aide de NSURLSessionTaskDelegate , mais par défaut, NSURLSession gère tous les détails.

Schéma de sérialisation


La sérialisation est le processus de transfert de données d'un type de stockage à un autre, sans perte de contenu. Par exemple, les données sont stockées sous forme binaire pour occuper moins d'espace, et lorsqu'elles sont envoyées sur un réseau, elles sont converties au format JSON (JavaScript Object Notation) universel, que nous déchiffrons déjà et traduisons en objets dans notre environnement de programmation.

Exemple JSON:

 { "name": "Martin Conte Mac Donell", "age": 29, "username": "fz" } 

Les accolades indiquent un dictionnaire et les objets à l'intérieur d'un dictionnaire sont représentés par des paires clé-valeur.

API (interface de programmation d'application)


Dans notre cas, l'API est représentée par l'adresse à partir de laquelle nous recevrons des blagues aléatoires et des formats de réponse JSON, que nous devons analyser en structures pratiques pour la manipulation

 http://api.icndb.com/jokes/random 

Exemple d'API icndb:

 { "type": "success", "value": { "id": 201, "joke": "Chuck Norris was what Willis was talkin' about" } } 

Maintenant pratique


L'ensemble du projet, comme la dernière fois, est implémenté en code, sans utiliser de storyboard. Tout le code est écrit dans 3 fichiers: AppDelegate.swift , MainViewController.swift et HTTPCommunication.swift . AppDelegate.swift contient la configuration générale de l'application. HTTPCommunication.swift configure la connexion (demande, session) et reçoit les données. Dans MainViewController.swift, ces données sont sérialisées pour la sortie et contiennent également du code d'interface utilisateur.

Créez un projet vide. Par souci de simplicité, nous écrivons une application uniquement pour l' iPhone . Nous supprimons ViewController.swift , Main.storyboard et dans Info.plist nous supprimons également le lien vers le storyboard, à savoir la ligne Nom de base du fichier de storyboard principal - Chaîne - Principal .

Par défaut, App Transport Security sur iOS bloque les téléchargements à partir d'Internet en utilisant http (pas https) normal , nous apportons donc des modifications à Info.plist , comme indiqué ci-dessous. Pour ce faire, ouvrez Info.plist en tant que code source , puis ajoutez le code suivant:

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

Par défaut, nous interdisons les téléchargements arbitraires: la clé NSAllowsArbitraryLoads est fausse. Mais nous ajoutons à titre d'exception notre domaine avec des blagues et tous les sous-domaines: les valeurs clés NSExceptionDomains.

Maintenant, dans AppDelegate.swift, nous réécrivons l' application (_: didFinishLaunchingWithOptions :) comme suit:

 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { self.window = UIWindow(frame: UIScreen.main.bounds) //  MainViewController   NavigationController, //     . let navC: UINavigationController = UINavigationController(rootViewController: MainViewController()) self.window?.rootViewController = navC self.window?.backgroundColor = UIColor.white self.window?.makeKeyAndVisible() return true } 


Créez le fichier HTTPCommunication.swift . Et nous y écrivons le code suivant.

 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) { } } 


Nous allons maintenant écrire le code de ces fonctions.

Copiez le code retrieveURL (_ url:, complétHandler :)

 //        , //     @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() } 


Copiez le 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.") } } 


Nous créons le fichier MainViewController.swift et copions le code suivant, qui crée l'interface nécessaire:

 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) ]) } } 


Nous avons compris l'interface, vous pouvez maintenant remplir la fonctionnalité.

Voici le 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.") } } } 


Exécutez maintenant l'application et obtenez le résultat suivant.

Pendant que nous attendons une blague du site.

image

Enfin, la blague est chargée et affichée.

image

Dans le prochain article, nous verrons la deuxième partie de l'application réécrite en swift, qui vous permet de recevoir de nouvelles blagues sans redémarrer le programme, ainsi que de voter pour des blagues.

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


All Articles