Interaction with the server through the API in iOS on Swift 3. Part 1

This article is an update of the article Retrieving Deleted Data in iOS , written in November 2015 using Objective-C and therefore morally sound. Now the code rewritten in Swift 3 and iOS 10 will be shown (the latest version is Swift 4.1 and iOS 11, but my computer no longer supports them).

Brief theory


Url format


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


This url from the example can be read as follows: http request with the GET method is sent to the google.com domain, to the root directory /, with two parameters q with the value Hello and safe with the value off.

http header


The browser converts the url string into the request header and body. For an http request, the body is empty and the header is as follows.

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

Request for server


First a request is created, then a connection is established, a request is sent and a response is received.

Session delegates


All UI operations (associated with the user interface) are performed in the main thread. You can't just take and stop this thread while some resource-intensive operation is being performed. Therefore, one of the solutions to this problem was the creation of delegates. Thus, the operations become asynchronous, and the main thread runs non-stop. When the necessary operation is performed, the corresponding delegate method will be called. The second solution to the problem is to create a new thread of execution.

As in the original book, we use the delegate so that the operations are divided between the methods more clearly. Although through the blocks, the code is more compact.

Description of the session delegate types


We use NSURLSessionDownloadDelegate and implement its URLSession: downloadTask: didFinishDownloadingToURL: method . That is, in essence, we are downloading data with a joke to the temporary storage, and when the download is complete, we call the delegate method for processing.

Transition to main stream


Data loading into temporary storage is not carried out in the main stream, but in order to use this data to change the UI, we will move to the main stream.

“Runaway” closure (@escaping)


Since, due to the implementation of the code, the closure that we pass to the method of loading data from the url, survives the method itself, Swift 3 needs to explicitly designate it as @escaping, and make unowned self so that the self link does not capture and hold in this closure. But these are the nuances of the implementation of the Swift language itself, and not the technonology of obtaining data by API.

Redirects (redirects)


In some cases, redirects occur. For example, if we have some short url, then when we enter it into the browser search bar, the browser first goes to the server, where this short url is decrypted and sent to us, and then we go to the target url by this full url. If necessary, we can control these redirects using NSURLSessionTaskDelegate , but by default NSURLSession itself handles all the details.

Serialization scheme


Serialization is the process of transferring data from one type of storage to another, without loss of content. For example, data is stored in binary form to take up less space, and when sent over the network, it is converted into a universal JSON (JavaScript Object Notation) format, which we already decrypt and translate into objects of our programming environment.

JSON example:

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

Braces denote a dictionary, and objects within a dictionary are represented by key-value pairs.

API (Application Programming Interface)


In our case, the API is represented by the address from which we will receive random jokes and JSON response formats, which we need to disassemble into convenient structures for manipulation.

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

Example icndb API:

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

And now practice


The whole project, like last time, is implemented in code, without using a storyboard. All code is written in 3 files: AppDelegate.swift , MainViewController.swift and HTTPCommunication.swift . AppDelegate.swift contains general application settings. HTTPCommunication.swift configures the connection (request, session) and retrieves data. In MainViewController.swift, this data is serialized for output, and also contains user interface code.

Create an empty project. For simplicity, we write an application only for the iPhone . Delete ViewController.swift , Main.storyboard and in Info.plist also delete the link to the storyboard, namely the line Main storyboard file base name - String - Main .

By default, App Transport Security in iOS blocks downloads from the Internet using standard http (not https), so we make changes to Info.plist , as shown below. To do this, open Info.plist as source code , then add the following code:

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

We, like the default, prohibit arbitrary downloads: the NSAllowsArbitraryLoads key is false. But we add as an exception our domain with jokes and all subdomains: key values ​​NSExceptionDomains.

Now in AppDelegate.swift we rewrite the application (_: didFinishLaunchingWithOptions :) as follows:

 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 } 


Create an HTTPCommunication.swift file. And we write the following code in it.

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


Now let's write the code of these functions.

Copy the retrieveURL code (_ 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() } 


Copy the 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.") } } 


Create the MainViewController.swift file and copy the following code, which creates the necessary interface:

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


Having dealt with the interface, now you can fill in the functionality.

Here is the code for 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.") } } } 


Now we run the application and get the following result.

While we are waiting for a joke from the site.

image

Finally, the joke is loaded and displayed.

image

In the next article we will look at the second part of the application rewritten to swift, which allows you to receive new jokes without restarting the program, and also vote for jokes.

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


All Articles