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:
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)
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.

Finalmente, el chiste se carga y se muestra.

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.