Alamofire 5 Tutorial for iOS: Getting Started

In this Alamofire tutorial, you’ll build an iOS companion app to perform networking tasks, send request parameters, decode/encode responses and more. By Corey Davis.

4.6 (54) · 3 Reviews

Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Using a Codable Data Model

But, how do you work with the JSON data returned? Working with JSON directly can be messy due to its nested structure, so to help with that, you’ll create models to store your data.

In the Project navigator, find the Networking group and create a new Swift file in that group named Film.swift.

Then, add the following code to it:

struct Film: Decodable {
  let id: Int
  let title: String
  let openingCrawl: String
  let director: String
  let producer: String
  let releaseDate: String
  let starships: [String]
  
  enum CodingKeys: String, CodingKey {
    case id = "episode_id"
    case title
    case openingCrawl = "opening_crawl"
    case director
    case producer
    case releaseDate = "release_date"
    case starships
  }
}

With this code, you’ve created the data properties and coding keys you need to pull data from the API’s film endpoint. Note how the struct is Decodable, which makes it possible to turn JSON into the data model.

The project defines a protocol — Displayable — to simplify showing detailed information later in the tutorial. You must make Film conform to it. Add the following at the end of Film.swift:

extension Film: Displayable {
  var titleLabelText: String {
    title
  }
  
  var subtitleLabelText: String {
    "Episode \(String(id))"
  }
  
  var item1: (label: String, value: String) {
    ("DIRECTOR", director)
  }
  
  var item2: (label: String, value: String) {
    ("PRODUCER", producer)
  }
  
  var item3: (label: String, value: String) {
    ("RELEASE DATE", releaseDate)
  }
  
  var listTitle: String {
    "STARSHIPS"
  }
  
  var listItems: [String] {
    starships
  }
}

This extension allows the detailed information display’s view controller to get the correct labels and values for a film from the model itself.

In the Networking group, create a new Swift file named Films.swift.

Add the following code to the file:

struct Films: Decodable {
  let count: Int
  let all: [Film]
  
  enum CodingKeys: String, CodingKey {
    case count
    case all = "results"
  }
}

This struct denotes a collection of films. As you previously saw in the console, the endpoint swapi.dev/api/films returns four main values: count, next, previous and results. For your app, you only need count and results, which is why your struct doesn’t have all properties.

The coding keys transform results from the server into all. This is because Films.results doesn’t read as nicely as Films.all. Again, by conforming the data model to Decodable, Alamofire will be able to convert the JSON data into your data model.

Note: For more information on Codable, see our tutorial on Encoding and Decoding in Swift.

Back in MainTableViewController.swift, in fetchFilms(), replace:

request.responseJSON { (data) in
  print(data)
}

With the following:

request.responseDecodable(of: Films.self) { (response) in
  guard let films = response.value else { return }
  print(films.all[0].title)
}

Now, rather than converting the response into JSON, you’ll convert it into your internal data model, Films. For debugging purposes, you print the title of the first film retrieved.

Build and run. In the Xcode console, you’ll see the name of the first film in the array. Your next task is to display the full list of movies.

Debug console which reads 'A New Hope'

Method Chaining

Alamofire uses method chaining, which works by connecting the response of one method as the input of another. This not only keeps the code compact, but it also makes your code clearer.

Give it a try now by replacing all of the code in fetchFilms() with:

AF.request("https://swapi.dev/api/films")
  .validate()
  .responseDecodable(of: Films.self) { (response) in
    guard let films = response.value else { return }
    print(films.all[0].title)
  }

This single line not only does exactly what took multiple lines to do before, but you also added validation.

From top to bottom, you request the endpoint, validate the response by ensuring the response returned an HTTP status code in the range 200–299 and decode the response into your data model. Nice! :]

Setting up Your Table View

Now, at the top of MainTableViewController, add the following:

var items: [Displayable] = []

You’ll use this property to store the array of information you get back from the server. For now, it’s an array of films but there’s more coolness coming soon! In fetchFilms(), replace:

print(films.all[0].title)

With:

self.items = films.all
self.tableView.reloadData()

This assigns all retrieved films to items and reloads the table view.

To get the table view to show the content, you must make some further changes. Replace the code in tableView(_:numberOfRowsInSection:) with:

return items.count

This ensures that you show as many cells as there are films.

Next, in tableView(_:cellForRowAt:) right below the declaration of cell, add the following lines:

let item = items[indexPath.row]
cell.textLabel?.text = item.titleLabelText
cell.detailTextLabel?.text = item.subtitleLabelText

Here, you set up the cell with the film name and episode ID, using the properties provided via Displayable.

Build and run. You’ll see a list of films:

tableview showing list of film titles

Now you’re getting somewhere! You’re pulling data from a server, decoding it into an internal data model, assigning that model to a property in the view controller and using that property to populate a table view.

But, as wonderful as that is, there’s a small problem: When you tap one of the cells, you go to a detail view controller which isn’t updating properly. You’ll fix that next.

Updating the Detail View Controller

First, you’ll register the selected item. Under var items: [Displayable] = [], add:

var selectedItem: Displayable?

You’ll store the currently-selected film to this property.

Now, replace the code in tableView(_:willSelectRowAt:) with:

selectedItem = items[indexPath.row]
return indexPath

Here, you’re taking the film from the selected row and saving it to selectedItem.

Now, in prepare(for:sender:), replace:

destinationVC.data = nil

With:

destinationVC.data = selectedItem

This sets the user’s selection as the data to display.

Build and run. Tap any of the films. You should see a detail view that is mostly complete.

detail view controller showing most film details

Fetching Multiple Asynchronous Endpoints

Up to this point, you’ve only requested films endpoint data, which returns an array of film data in a single request.

If you look at Film, you’ll see starships, which is of type [String]. This property does not contain all of the starship data, but rather an array of endpoints to the starship data. This is a common pattern programmers use to provide access to data without providing more data than necessary.

For example, imagine that you never tap “The Phantom Menace” because, you know, Jar Jar. It’s a waste of resources and bandwidth for the server to send all of the starship data for “The Phantom Menace” because you may not use it. Instead, the server sends you a list of endpoints for each starship so that if you want the starship data, you can fetch it.