Interacción con el servidor a través de la API en iOS en Swift 3. Parte 1

Este artículo es una actualización del artículo Obtención de datos remotos en iOS , escrito en noviembre de 2015 utilizando Objective-C y, por lo tanto, moralmente obsoleto. Ahora, se proporcionará el código que se reescribió en Swift 3 y iOS 10 (la última versión es Swift 4.1 y iOS 11, pero mi computadora ya no los admite).

Breve teoría


Formato de URL


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

  • http es un protocolo que determina por qué estándar se realiza una solicitud. Más opciones: https, ftp, archivo
  • www.google.com - nombre de dominio
  • / - directorio donde se encuentran los recursos que necesitamos.
  • Después del signo de interrogación (?) Vaya a los parámetros q = Hola y seguro = apagado. Consisten en pares clave-valor.
  • La solicitud también indica un método que indica cómo el servidor debe manejar esta solicitud. Por defecto, este es un método GET.

Esta url del ejemplo se puede leer de la siguiente manera: se envía una solicitud http con el método GET a google.com, en el directorio raíz /, con dos parámetros q con Hello y safe con off.

encabezado http


El navegador convierte la cadena de url en el encabezado y el cuerpo de la solicitud. Para una solicitud http, el cuerpo está vacío y el encabezado es el siguiente

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

Diagrama de solicitud del servidor


Primero, se crea una solicitud, luego se establece una conexión, se envía una solicitud y se recibe una respuesta.

Delegados de sesión


Todas las operaciones de IU (asociadas con la interfaz de usuario) se realizan en el hilo principal. No puede simplemente tomar y detener este hilo mientras se realiza algún tipo de operación de uso intensivo de recursos. Por lo tanto, una de las soluciones a este problema fue crear delegados. Por lo tanto, las operaciones se vuelven asíncronas y el subproceso principal se ejecuta sin parar. Cuando se complete la operación necesaria, se llamará al método delegado correspondiente. La segunda solución es crear un nuevo hilo de ejecución.

Como en el libro original, usamos un delegado para que las operaciones se dividan más claramente entre los métodos. Aunque el código es más compacto a través de bloques.

Descripción de los tipos de delegados de sesión.


Utilizamos NSURLSessionDownloadDelegate e implementamos su método URLSession: downloadTask: didFinishDownloadingToURL :. Es decir, de hecho, descargamos los datos con una broma en un almacenamiento temporal, y cuando se completa la descarga, llamamos al método delegado para el procesamiento.

Transición a la transmisión principal


La carga de datos en el almacenamiento temporal no se lleva a cabo en la secuencia principal, pero para usar esta información para cambiar la interfaz de usuario, iremos a la secuencia principal.

Cierre fuera de control (@escaping)


Dado que, debido a la implementación del código, el cierre que pasamos al método de carga de datos desde la url sobrevivirá al método en sí, para Swift 3 es necesario designarlo explícitamente @escaping y self para que no sea propietario para que el enlace automático no capture y retenga en este cierre. Pero estos son los matices de la implementación del lenguaje Swift en sí, y no la tecnología de recibir datos por API.

Redirecciones (redirecciones)


En algunos casos, se producen redireccionamientos. Por ejemplo, si tenemos alguna url corta, cuando la ingresamos en la barra de búsqueda del navegador, el navegador primero va al servidor, donde esta url corta se descifra y nos envía, y luego vamos al servidor de destino usando esta url completa. Si es necesario, podemos controlar estos redireccionamientos utilizando NSURLSessionTaskDelegate , pero de forma predeterminada, NSURLSession maneja todos los detalles.

Esquema de serialización


La serialización es el proceso de transferir datos de un tipo de almacenamiento a otro, sin pérdida de contenido. Por ejemplo, los datos se almacenan en forma binaria para ocupar menos espacio, y cuando se envían a través de una red, se convierten a un formato universal JSON (JavaScript Object Notation), que ya desciframos y traducimos en objetos en nuestro entorno de programación.

Ejemplo de JSON:

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

Las llaves indican un diccionario, y los objetos dentro de un diccionario están representados por pares clave-valor.

API (interfaz de programación de aplicaciones)


En nuestro caso, la API está representada por la dirección desde donde recibiremos chistes aleatorios y formatos de respuesta JSON, que debemos analizar en estructuras convenientes para la manipulación

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

Ejemplo de API icndb:

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

Ahora practica


Todo el proyecto, como la última vez, se implementa en código, sin usar un guión gráfico. Todo el código está escrito en 3 archivos: AppDelegate.swift , MainViewController.swift y HTTPCommunication.swift . AppDelegate.swift contiene la configuración general de la aplicación. HTTPCommunication.swift configura la conexión (solicitud, sesión) y recibe datos. En MainViewController.swift, estos datos se serializan para la salida y también contienen código de interfaz de usuario.

Crea un proyecto vacío. Por simplicidad, estamos escribiendo una aplicación solo para iPhone . Eliminamos ViewController.swift , Main.storyboard y en Info.plist también eliminamos el enlace al guión gráfico, es decir, el nombre base del archivo de guión principal de línea - Cadena - Principal .

De forma predeterminada, App Transport Security en iOS bloquea las descargas de Internet mediante http (no https), por lo que realizamos cambios en Info.plist , como se muestra a continuación. Para hacer esto, abra Info.plist como código fuente , luego agregue el siguiente código:

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

Nosotros, por defecto, prohibimos las descargas arbitrarias: la clave NSAllowsArbitraryLoads es falsa. Pero agregamos como excepción nuestro dominio con bromas y todos los subdominios: valores clave NSExceptionDomains.

Ahora en AppDelegate.swift reescribimos la aplicación (_: didFinishLaunchingWithOptions :) de la siguiente manera:

 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 } 


Cree el archivo HTTPCommunication.swift . Y escribimos el siguiente código en él.

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


Ahora escribiremos el código de estas funciones.

Copie el código retrieveURL (_ url:, completeHandler :)

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


Copie el código 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.") } } 


Creamos el archivo MainViewController.swift y copiamos el siguiente código, que crea la interfaz necesaria:

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


Descubrimos la interfaz, ahora puede completar la funcionalidad.

Aquí está el código 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.") } } } 


Ahora ejecute la aplicación y obtenga el siguiente resultado.

Mientras esperamos una broma del sitio.

imagen

Finalmente, el chiste se carga y se muestra.

imagen

En el próximo artículo, veremos la segunda parte de la aplicación, reescrita rápidamente, que le permite recibir nuevos chistes sin reiniciar el programa, así como votar por los chistes.

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


All Articles