The first episodes of this series have almost exclusively focused on creating a foundation for the project. While this may seem tedious or even premature, it allows anyone working on the project to focus on writing code. Automation is an important part of modern software development and it pays to set aside some time to create a foundation for the project you're working on.

This project adopts the MVVM-C or Model-View-ViewModel-Coordinator pattern. It's important to choose an architecture for your project as early as possible to avoid messy code and unnecessary rounds of refactoring. I have covered the Model-View-ViewModel and Coordinator patterns extensively on Cocoacasts so I won't cover them in detail in this series.

Populating the Feed

This and the next episodes focus on populating the feed view controller. This view controller lists the most recent episodes in chronological order. To make that possible, the client fetches a list of episodes from the Cocoacasts API and uses that data to populate the feed view controller. This seems straightforward, but there are quite a few steps we need to take. Let's break it down.

(1) The client needs to make an API request to fetch the data. (2) The Cocoacasts API returns a JSON response, which means that the client needs to convert the JSON response into model objects. (3) The feed view controller uses the model objects to populate a collection view.

This is high level overview of what we cover in this and the next episodes. We also create a dedicated object to handle the API request. Because the project adopts the MVVM-C pattern, we create a view model that is responsible for managing the model objects and providing the feed view controller with the data it needs to populate its collection view.

Exploring the API

Since we are interacting with a remote API, we start by making a request to the Cocoacasts API to understand the format of the request and the format of the response. You can use the command line or you can use a tool with a graphical user interface, such as Paw. I recommend using the tool you feel most comfortable using.

If you control the API you are interacting with, it is convenient to run an instance of the API on your machine. This speeds up development and it also makes debugging any issues on the backend easier. Once you start testing the implementation, you switch to a staging environment. It goes without saying that you should never ship a feature you only tested locally.

To fetch the list of recent episodes, I make a GET request to the episodes endpoint. The endpoint supports paging and accepts several optional parameters. We worry about that later. The client identifies itself using an API key. API keys come in various shapes and forms. Some API keys are required for security purposes while others merely identify the client.

As I mentioned in Protecting the Secrets of Your Mobile Application, you have public and private secrets. The API key used in this request is a public secret. It is required, but it doesn't offer any meaningful security. The backend uses it to identify the client. The request includes a header field with name X-API-Key and the API key as its value.

Fetching Data From the Cocoacasts API

The API returns an array of episodes. Each episode defines a number of properties, such as the identifier of the episode, a title, the publication date, and a thumbnail URL. Notice that the thumbnail has an SVG extension. The UIKit framework doesn't have native support for rendering SVG files. We resolve that problem using a third party solution.

[
  {
    "id": 317,
    "plus": false,
    "title": "Debugging Applications With Reveal",
    "excerpt": "In [Debugging Applications With Xcode](https://cocoacasts.com/series/debugging-applications-with-xcode), we explored [Xcode's built-in view debugger](https://cocoacasts.com/debugging-applications-with-xcode-view-and-view-controller-debugging). While I occasionally use Xcode's view debugger to debug user interface issues, I mostly use [Reveal](https://revealapp.com/), a third party application developed and maintained by [Itty Bitty Apps](http://www.ittybittyapps.com/). In this episode, I show you how Reveal compares to Xcode's built-in view debugger and why I prefer Reveal over Xcode to debug user interface issues.",
    "swift": 4,
    "xcode": 9,
    "platform_name": "ios",
    "platform_version": 11,
    "author": "Jim Johnson",
    "thumbnail_url": "https://cocoacasts.s3.amazonaws.com/debugging-applications-with-reveal/debugging-applications-with-reveal.svg",
    "collection": null,
    "published_at": "2018-04-17T04:30:00+00:00"
  },
  ...
]

We now have a better idea of the request we need to send and the format of the response. The next step is performing the request using the Foundation framework.

Using the URLSession API

There are many third party libraries that focus on networking. In this series, we use Foundation's URLSession API. It is easy to use. By using a native API, we don't rely on a third party for a core component of the project.

Playgrounds can be useful for prototyping components of a project. We use a playground to perform the API request, parse the JSON response, and create a dedicated object that encapsulates this aspect of the project. We start with a blank playground and add an import statement for the Foundation framework.

import Foundation

We first create the URL for the request. As long as we are working in a playground, we don't focus on writing safe or elegant code. The goal is to create something that works. We refine the implementation once we have a rough implementation. We use a string literal to create the URL object and forced unwrap the result of the initialization.

import Foundation

// Define URL
let url = URL(string: "http://0.0.0.0:3000/api/episodes")!

We use the URL object to create a URLRequest object. We define a variable to make the URLRequest object mutable.

import Foundation

// Define URL
let url = URL(string: "http://0.0.0.0:3000/api/episodes")!

// Create Request
var request = URLRequest(url: url)

Adding the header field for the API key is straightforward. We invoke the addValue(_:forHTTPHeaderField:) method, passing in the value and the name of the header field.

import Foundation

// Define URL
let url = URL(string: "http://0.0.0.0:3000/api/episodes")!

// Create Request
var request = URLRequest(url: url)

// Add X-API-Key Header Field
request.addValue("9e8d3f9fd2ce713bb1ca8e60021e09d0dc6fb654", forHTTPHeaderField: "X-API-Key")

To perform the API request, we need to create a data task, an instance of the URLSessionDataTask class. We ask the shared URL session for a data task by passing it the URL request and a completion handler. The completion handler is executed when the request completes, successfully or unsuccessfully. To make sure everything works, we print the number of bytes that are returned by the API. We invoke resume() on the data task to initiate the request.

import Foundation

// Define URL
let url = URL(string: "http://0.0.0.0:3000/api/episodes")!

// Create Request
var request = URLRequest(url: url)

// Add X-API-Key Header Field
request.addValue("9e8d3f9fd2ce713bb1ca8e60021e09d0dc6fb654", forHTTPHeaderField: "X-API-Key")

// Create and Initiate Data Task
URLSession.shared.dataTask(with: request) { (data, response, error) in
    print(data?.count)
}.resume()

Let's execute the contents of the playground and inspect the output in the console. We should see a number in the console if the request was successful. That looks good.

Optional(15554)

Parsing the Response

The next step is converting the JSON response into model objects. Open the Project Navigator on the right and create a file in the Sources group. Name the file Episode.swift.

Creating Episode.swift

We define a struct with name Episode. To make it accessible in the playground, we need to declare it publicly by prepending the public keyword.

import Foundation

public struct Episode {

}

We start by defining the properties of the Episode struct. Not every Cocoacasts episode is part of a collection. That is why the collection property is of type String?.

import Foundation

public struct Episode {

    // MARK: - Properties

    let id: Int
    let plus: Bool
    let title: String
    let excerpt: String
    let author: String
    let thumbnailUrl: URL
    let collection: String?
    let publishedAt: Date

    let swift: Int
    let xcode: Int
    let platformName: String
    let platformVersion: Int

}

I control the Cocoacasts API and that has several benefits. The most interesting benefit is that I can make sure that the data the Cocoacasts API returns is tailored to the Cocoacasts client. The Decodable protocol makes converting the JSON response into model objects painless. The Decodable and Encodable protocols are powerful and can handle complex scenarios. Because the JSON response matches the interface of the Episode struct, the heavy lifting is handled by the Decodable protocol and the JSONDecoder class. The only thing we need to do is conform the Episode struct to the Decodable protocol.

import Foundation

public struct Episode: Decodable {

    // MARK: - Properties

    let id: Int
    let plus: Bool
    let title: String
    let excerpt: String
    let author: String
    let thumbnailUrl: URL
    let collection: String?
    let publishedAt: Date

    let swift: Int
    let xcode: Int
    let platformName: String
    let platformVersion: Int

}

With the Episode struct ready to use, it's time to revisit the playground and implement the completion handler of the data task. We use optional binding to safely unwrap the Data instance that is passed to the completion handler. In the else clause of the if-else statement, we print any errors to the console. Don't worry about errors handling for now. We improve that later.

// Create and Initiate Data Task
URLSession.shared.dataTask(with: request) { (data, response, error) in
    if let data = data {

    } else {
        if let error = error {
            print("Unable to Fetch Episodes \(error)")
        } else {
            print("Unable to Fetch Episodes")
        }
    }
}.resume()

Decoding the JSON response is a throwing operation and should be performed in a do-catch statement. We start by initializing an instance of the JSONDecoder class.

// Create and Initiate Data Task
URLSession.shared.dataTask(with: request) { (data, response, error) in
    if let data = data {
        do {
            // Initialize JSON Decoder
            let decoder = JSONDecoder()

        } catch {

        }

    } else {
        if let error = error {
            print("Unable to Fetch Episodes \(error)")
        } else {
            print("Unable to Fetch Episodes")
        }
    }
}.resume()

The next step is configuring the JSON decoder. The first property we set is dateDecodingStrategy. The dates returned by the Cocoacasts API conform to the ISO 8601 standard. That standard is supported out of the box by the JSONDecoder class.

// Initialize JSON Decoder
let decoder = JSONDecoder()

// Configure JSON Decoder
decoder.dateDecodingStrategy = .iso8601

The keys of the JSON response are in snake case and we need to inform the JSON decoder about that by setting its keyDecodingStrategy property to convertFromSnakeCase. It is nice to see that converting from snake case is also supported out of the box by the JSONDecoder class.

// Initialize JSON Decoder
let decoder = JSONDecoder()

// Configure JSON Decoder
decoder.dateDecodingStrategy = .iso8601
decoder.keyDecodingStrategy = .convertFromSnakeCase

The next step is decoding the data of the JSON response. We invoke the decode(_:from:) method on the JSONDecoder instance, passing in the type of the result, an array of Episode objects, and the Data object that is passed to the completion handler. As I mentioned earlier, decoding the JSON response is a throwing operation. We prefix the decode(_:from:) call with the try keyword. We store the result in a constant with name episodes.

// Initialize JSON Decoder
let decoder = JSONDecoder()

// Configure JSON Decoder
decoder.dateDecodingStrategy = .iso8601
decoder.keyDecodingStrategy = .convertFromSnakeCase

// Decode JSON Response
let episodes = try decoder.decode([Episode].self, from: data)

We print the number of Episode objects to the console if decoding the JSON response is successful. We print any errors to the console in the catch clause of the do-catch statement.

do {
    // Initialize JSON Decoder
    let decoder = JSONDecoder()

    // Configure JSON Decoder
    decoder.dateDecodingStrategy = .iso8601
    decoder.keyDecodingStrategy = .convertFromSnakeCase

    // Decode JSON Response
    let episodes = try decoder.decode([Episode].self, from: data)

    print(episodes.count)
} catch {
    print("Unable to Decode Response \(error)")
}

Let's execute the contents of the playground to make sure the list of episodes is fetched from the Cocoacasts API and converted into Episode objects. That is looking good.

20

What's Next?

We have successfully fetched and parsed episodes from the Cocoacasts API in this episode. Working with the URLSession API isn't difficult for common operations and the Decodable protocol makes converting the JSON response into model objects straightforward.

It is true that parsing a JSON response can sometimes be a nightmare, especially if the JSON response doesn't conform to commonly used standards. The Decodable protocol is powerful and flexible, though. It is capable of handling most scenarios with surprising little effort from your end.

In the next episode, we create an object that encapsulates the tasks we are currently performing in the playground. The goal is to create an object with an elegant and easy to use API.