REST API in Swift 4 using URLSession and JSONDecoder

Category: Swift
REST API in Swift 4 using URLSession and JSONDecoder

Apple announced Swift 4 as part of Xcode 9 at WWDC 2017. Swift 4 is a major release that is intended to be completed in the fall of 2017. It brings some really nice improvements to existing Swift 3 features as well as stability. In this article we will take a look at working with REST API in Swift 4 using URLSession and JSONDecoder. The later was introduced with Swift 4.

If you are familiar with Swift at have developed couple of applications you definitely have dealt with REST API where you wanted to grab some data from a server and display it into a UITableView or a UICollectionView.

Most of developers used third party frameworks such as SwiftyJson and Alamofire in order to work with REST APIs. The reason for this, most of the time, was that parsing JSON using Swift was very tedious. More precisely - you had to set up initializer in your Model, had to do loops to assign values in your controller, had to typecast values and so on. You could always copy/paste your code, but still it was overwhelming. With Swift 4 all you will need to do is to write just a single line to decode and parse JSON.

Working with REST API is essential for a mobile application and Swift 4 has an amazing addition: JSONDecoder and JSONEncoder. As you might guess from the names of the classes these object will be used for decoding and encoding instances of a data type from/to JSON objects.

This article is divided into two parts - URLSession and JSONDecoder. First we will implement URLSession to grab the data using an HTTP request and then we will take a look at implementation of JSONDecoder class to convert JSON into data type we need and then feed it to UICollectionView. As you progress through the article you will see that all it takes to decode JSON data is simply just ONE line of code.

To get started let’s create a single view application and set up UICollectionView. In my case I prefer doing everything (building UI) programmatically rather than using Storyboards.

I created custom UICollectionViewCell named ArticleViewCell. Inside I placed two labels for the article title and description.

For the model I created a struct named Article and inside I defined two parameters articleTitle and articleDescription.

You can check out the files below:

MainViewController.swift

//
//  MainViewController.swift
//  Created by Mikheil Gotiashvili on 7/11/17.
//  Copyright © 2017 Mikheil Gotiashvili. All rights reserved.
//

import UIKit

class MainViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {

    fileprivate let cellId = "cellId"
    //Array of type Article to store articles data after parsing JSON
    var articles:[Article]?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        navigationItem.title = "REST API"
        
        collectionView?.register(ArticleViewCell.self, forCellWithReuseIdentifier: cellId)
        collectionView?.backgroundColor = .white
        
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 5
    }
    
    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! ArticleViewCell
        return cell
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: view.bounds.width, height: 100)
    }

}

ArticleViewCell.swift

//
//  ArticleViewCell.swift
//  Created by Mikheil Gotiashvili on 7/13/17.
//  Copyright © 2017 Mikheil Gotiashvili. All rights reserved.
//

import UIKit

class ArticleViewCell: UICollectionViewCell {
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    let articleTitle:UILabel = {
        let title = UILabel()
        title.translatesAutoresizingMaskIntoConstraints = false
        return title
    }()
    
    let articleDescription: UILabel = {
        let description = UILabel()
        description.translatesAutoresizingMaskIntoConstraints = false
        return description
    }()
    
    func setupView(){
        //Align Title
        addSubview(articleTitle)
        articleTitle.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
        articleTitle.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
        articleTitle.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true
        articleTitle.heightAnchor.constraint(equalToConstant: 50).isActive = true
        //Align Description
        addSubview(articleDescription)
        articleDescription.topAnchor.constraint(equalTo: articleTitle.bottomAnchor).isActive = true
        articleDescription.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
        articleDescription.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true
        articleDescription.heightAnchor.constraint(equalToConstant: 50).isActive = true
        
    }
    
}

ArticleModel.swift

//
//  ArticleModel.swift
//  Created by Mikheil Gotiashvili on 7/13/17.
//  Copyright © 2017 Mikheil Gotiashvili. All rights reserved.
//

import UIKit

struct Article {
    let title: String
    let description: String
}

Now that we have everything in place let's start writing actual code to grab JSON data from a URL and later decode it for further use.

URLSesssion

To make an HTTP request and get data from a URL we will be using URLSession provided by Apple’s Foundation framework. The URLSession class and related classes provide an API for downloading or uploading content. URLSession has it’s configuration and can be configured for some specific uses but in this example we will stick with the most common and basic implementation.

Apple docs: With the URLSession API, your app creates one or more sessions, each of which coordinates a group of related data transfer tasks. For example, if you are writing a web browser, your app might create one session per tab or window, or one session for interactive use and another session for background downloads. Within each session, your app adds a series of tasks, each of which represents a request for a specific URL (following HTTP redirects if necessary). 
The tasks within a given URL session share a common session configuration object, which defines connection behavior, such as the maximum number of simultaneous connections to make to a single host, whether to allow connections over a cellular network, and so on. The behavior of a session is determined in part by which method you call when creating its configuration object.

In our example we use the singleton shared session (which has no configuration object) which is used for basic requests. It is not as customizable as sessions, but it serves as a good starting point if you have very limited requirements. You access this session by calling the shared class method.

Then we use instance method dataTask(with: URL, completionHandler: (Data?, URLResponse?, Error?) -> Void) which creates a task that retrieves the contents of the specified URL and then calls a handler upon completion. The completion handler returns three variables: data, response and error. data contains the retrieved data from the request, response contains server’s response code (ex. in case everything goes fine it will contain code 200, and if it fails code 400, 404 or smth else) and error contains the error message. Before you grab the data and decode it, it’s a good practice to check for response code for 200 or error for errors. If the error is equal to nil we continue working with data.

data variable is returned as optional value and we need to unwrap it before we continue.

Here’s the code we have written so far (you can either put directly into ViewDidLoad method or create a function and call it from ViewDidLoad):

MainViewController.swift

override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        navigationItem.title = "REST API"
        
        collectionView?.register(ArticleViewCell.self, forCellWithReuseIdentifier: cellId)
        collectionView?.backgroundColor = .white
        
//Implementing URLSession let urlString = "https://swift.mrgott.pro/blog.json" guard let url = URL(string: urlString) else { return } URLSession.shared.dataTask(with: url) { (data, response, error) in if error != nil { print(error!.localizedDescription) } guard let data = data else { return } }.resume() //End implementing URLSession }

If you take a look at the code you will see that in this example the request URL is the address of my other website where JSON file is located. Finally we got to the point where Swift 4 fun begins. Let’s implement JSONDecoder class.

JSONDecoder

JSONDecoder object contains a method decode that returns a value of the type you specify, decoded from a JSON object. Declaration of decode method from the Apple docs is the following: func decode(_ type: T.Type, from data: Data) throws -> T where T : Decodable

So, what does this mean? Well, it takes two parameters type and data. It’s easy to guess that data is the data variable we received from the HTTP request we made previously and unwrapped.

What is the type parameter that we should pass? Well this is where fun begins!
Recall we created a model called Article that contains two parameters inside articleTitle and articleDescription. We will pass Article as a type and Swift 4 is so clever that it will match decoded data to our model and assign corresponding values to it.

If you open the URL which we requested you will see that it contains an array of dictionaries of articles that have following parameters: title, description and thumbnail_image_name.

NOTE: In this example we have an array of articles and as a type we pass [Article] telling decoder that we are expecting an array. If you were to receive just one article, for example as a detailed view for an article you could pass just Article and it would return just the dictionary with the parameters of a single article. If your JSON file contains an array but you forgot to put square brackets around the type you will get en error :)

NOTE: Swift 4 will match the values from the JSON file to corresponding parameters in your model. In my case thumbnail_image_name won’t be matched with anything as far as I don’t have it in my model. On the contrary, if JSON file was missing any of the parameters that I have in my model: say JSON doesn’t have a title then we would get an error saying that it was looking for the title but couldn’t find it. If you want to avoid this kind of error you can just simply declare your variable inside your model as Optionals by simply placing questionmark let title:String?

Back to the decode method declaration. Documentation states that it throws - meaning that the call can throw an error and we have to mark it with try. And as always if the call might throw an error we have to wrap it into do catch block.

Once again back to decode method declaration. At the end of the line there’s where T: Decodable. It means that the type we pass, in our case Article model should conform to either Codable or Decodable protocol. Decodable is a type that can decode itself from an external representation.

You can use Decodable in case if you only decode JSON. Use Codable if you use the model for both - decoding and encoding. It’s up to you.

Codable is used to support both encoding and decoding, declare conformance to Codable, which combines the Encodable and Decodable protocols. This process is known as making your types codable. Adding Codable to the inheritance list for Article triggers an automatic conformance that satisfies all of the protocol requirements from Encodable and Decodable.

I recommend you taking a look at Encoding and Decoding Custom Types.

Here’s the code so far:

MainViewController.swift

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        navigationItem.title = "REST API"
        
        collectionView?.register(ArticleViewCell.self, forCellWithReuseIdentifier: cellId)
        collectionView?.backgroundColor = .white
        
        let urlString = "https://swift.mrgott.pro/blog.json"
        guard let url = URL(string: urlString) else { return }
        
        URLSession.shared.dataTask(with: url) { (data, response, error) in
            if error != nil {
                print(error!.localizedDescription)
            }
            
            guard let data = data else { return }
            //Implement JSON decoding and parsing
            do {
                //Decode retrived data with JSONDecoder and assing type of Article object
                let articlesData = try JSONDecoder().decode([Article].self, from: data)
                
                //Get back to the main queue
                DispatchQueue.main.async {
                    //print(articlesData)
                    self.articles = articlesData
                    self.collectionView?.reloadData()
                }
                
            } catch let jsonError {
                print(jsonError)
            }
            
            
        }.resume()
        
    }

ArticleMode.swift

import UIKit

struct Article: Codable {
    let title: String
    let description: String
}

At this point if you do print(articlesData) and run the application console will output decoded array from the JSON file.

Now we are ready to pass the decoded data to our UICollectionView for display.
As you might recall URLSession operates in the background thread and we need to get back to the main thread and then reload UICollectionView.

The following changes have been made to the methods inside our UICollectionViewController (see comments in code):

MainViewController.swift

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        //We check whether articles variable count is more than zero and set the number of number of items in section 
        return articles?.count ?? 0
    }
    
    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! ArticleViewCell
        //We pass single article data to article variable inside ArticleViewCell according to the current indexPath
        cell.article = articles?[indexPath.item]
        return cell
    }

And we also need to take the single article values and assign them to the labels:

ArticleViewCell.swift

class ArticleViewCell: UICollectionViewCell {
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    //We added article variable of type Article model and receive article data passed from MainViewController's above mentioned method
    var article:Article? {
        didSet {
            articleTitle.text = article?.title
            articleDescription.text = article?.description
        }
    }
    
    let articleTitle:UILabel = {
   //code continues

Hopefully everything went just fine and your UICollectionView now displays the data from the JSON file.

Conclusion

So here we are! We have a fully working application with REST API and you might have noticed that decoding JSON data has become very simple and fun with Swift 4 and some might change their minds and use native frameworks instead of third party frameworks. I hope you enjoyed this article. In case you have any questions, remarks or suggestions please feel free to send me an email and follow me on Twitter. Cheers!