Interação com o servidor por meio da API no iOS no Swift 3. Parte 1

Este artigo é uma atualização para o artigo Obtendo dados remotos no iOS , escrito em novembro de 2015 usando o Objective-C e, portanto, moralmente obsoleto. Agora, será fornecido o código que foi reescrito para Swift 3 e iOS 10 (a versão mais recente é Swift 4.1 e iOS 11, mas meu computador não os suporta mais).

Breve teoria


Formato de URL


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

  • http é um protocolo que determina por qual padrão uma solicitação é feita. Mais opções: https, ftp, arquivo
  • www.google.com - nome de domínio
  • / - diretório onde estão localizados os recursos que precisamos.
  • Após o ponto de interrogação (?) Vá para os parâmetros q = Hello & safe = off. Eles consistem em pares de valores-chave.
  • A solicitação também indica um método que informa como o servidor deve lidar com essa solicitação. Por padrão, este é um método GET.

Este URL do exemplo pode ser lido da seguinte forma: uma solicitação http com o método GET é enviada para google.com, no diretório raiz /, com dois parâmetros q com Hello e seguro com off.

cabeçalho http


O navegador converte a sequência de URLs no cabeçalho e no corpo da solicitação. Para uma solicitação http, o corpo está vazio e o cabeçalho é o seguinte

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

Diagrama de solicitação do servidor


Primeiro, uma solicitação é criada, então uma conexão é estabelecida, uma solicitação é enviada e uma resposta é recebida.

Delegados da sessão


Todas as operações da interface do usuário (associadas à interface do usuário) são executadas no encadeamento principal. Você não pode simplesmente interromper esse encadeamento enquanto algum tipo de operação que consome muitos recursos está sendo executada. Portanto, uma das soluções para esse problema foi criar delegados. Portanto, as operações se tornam assíncronas e o thread principal é executado sem parar. Quando a operação necessária for concluída, o método delegado correspondente será chamado. A segunda solução é criar um novo segmento de execução.

Como no livro original, usamos um delegado para que as operações sejam divididas mais claramente entre os métodos. Embora o código seja mais compacto através de blocos.

Descrição dos tipos de delegados da sessão


Usamos NSURLSessionDownloadDelegate e implementamos seu método URLSession: downloadTask: didFinishDownloadingToURL :. Na verdade, baixamos os dados com uma piada para um armazenamento temporário e, quando o download é concluído, chamamos o método delegate para processamento.

Transição para o fluxo principal


O carregamento de dados no armazenamento temporário não é realizado no fluxo principal, mas, para usar esses dados para alterar a interface do usuário, iremos para o fluxo principal.

Fechamento em fuga (@escapamento)


Como, devido à implementação do código, o fechamento que passamos para o método de carregamento de dados do url sobreviverá ao próprio método, para o Swift 3, é necessário designá-lo explicitamente @escaping e self para torná-lo não proprietário, para que o auto link não capture e mantenha esse fechamento. Mas essas são as nuances da implementação da própria linguagem Swift, e não a tecnologia de recebimento de dados pela API.

Redirecionamentos (redirecionamentos)


Em alguns casos, os redirecionamentos ocorrem. Por exemplo, se tivermos um URL curto, quando o inserirmos na barra de pesquisa do navegador, o navegador primeiro acessará o servidor, onde esse URL curto é descriptografado e enviado para nós e, em seguida, o servidor de destino usando esse URL completo. Se necessário, podemos controlar esses redirecionamentos usando NSURLSessionTaskDelegate , mas, por padrão, o NSURLSession lida com todos os detalhes.

Esquema de serialização


Serialização é o processo de transferência de dados de um tipo de armazenamento para outro, sem perda de conteúdo. Por exemplo, os dados são armazenados em formato binário para ocupar menos espaço e, quando enviados pela rede, são convertidos para um formato universal JSON (JavaScript Object Notation), que já descriptografamos e convertemos em objetos em nosso ambiente de programação.

Exemplo JSON:

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

Colchetes indicam um dicionário e objetos dentro de um dicionário são representados por pares de valores-chave.

API (interface de programação de aplicativos)


No nosso caso, a API é representada pelo endereço de onde receberemos piadas aleatórias e formatos de resposta JSON, que precisamos analisar em estruturas convenientes para manipulação

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

Exemplo de API do icndb:

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

Agora pratique


Todo o projeto, como da última vez, é implementado em código, sem usar um storyboard. Todo o código é escrito em 3 arquivos: AppDelegate.swift , MainViewController.swift e HTTPCommunication.swift . AppDelegate.swift contém a configuração geral do aplicativo. HTTPCommunication.swift configura a conexão (solicitação, sessão) e recebe dados. No MainViewController.swift, esses dados são serializados para saída e também contêm código da interface do usuário.

Crie um projeto vazio. Para simplificar, estamos escrevendo um aplicativo apenas para iPhone . Excluímos ViewController.swift , Main.storyboard e no Info.plist também excluímos o link para o storyboard, ou seja, a linha Nome principal da base do arquivo de storyboard - String - Main .

Por padrão, o App Transport Security no iOS bloqueia downloads da Internet usando http normal (não https), portanto, fazemos alterações no Info.plist , conforme mostrado abaixo. Para fazer isso, abra Info.plist como um código-fonte e adicione o seguinte 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> 

Por padrão, proibimos downloads arbitrários: a chave NSAllowsArbitraryLoads é falsa. Mas adicionamos como exceção nosso domínio com piadas e todos os subdomínios: valores-chave NSExceptionDomains.

Agora, em AppDelegate.swift, reescrevemos application (_: didFinishLaunchingWithOptions :) da seguinte maneira:

 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 } 


Crie o arquivo HTTPCommunication.swift . E escrevemos o seguinte código nele.

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


Agora vamos escrever o código dessas funções.

Copie o 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 o 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.") } } 


Criamos o arquivo MainViewController.swift e copiamos o seguinte código, que cria a interface necessária:

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


Nós descobrimos a interface, agora você pode preencher a funcionalidade.

Aqui está o 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.") } } } 


Agora execute o aplicativo e obtenha o seguinte resultado.

Enquanto aguardamos uma piada do site.

imagem

Finalmente, a piada é carregada e exibida.

imagem

No próximo artigo, veremos a segunda parte do aplicativo reescrita rapidamente, que permite que você receba novas piadas sem reiniciar o programa, bem como vote em piadas.

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


All Articles