Lecture Handout

This lecture covers the second half of networking: decoding a response from the Internet.

Networking & Codable

Last week we talked about how to make HTTP requests to allow your app to communicate with the Internet. Today we'll talk about how to decode the responses and put them on the screen!

Decoding Responses from the Internet

Starting in Swift 4, there’s a protocol called Codable that objects, structs, and enums can conform to to make them encodable and decodable from JSON. Codable items can contain an enum for the coding keys of type String and CodingKey in order to decode the values from JSON.

We'll refer to the JSON response example from last lecture again today:

{
  "title": "HTTP, Networking, and JSON",
  "year_created": 2020,
  "success": true,
  "topics": [ "HTTP", "Networking", "JSON" ],
}

If we wanted to decode this example JSON response into a model of type Lecture, we could create a Lecture struct that conforms to Codable as follows:

struct Lecture: Codable {
  var title: String
  var yearCreated: Int
  var success: Bool
  var topics: [String]
}

Notice how the names of our variables match the fields in our JSON. This is done on purpose! With Codable, the names of the variables are expected to exactly match the keys in the JSON response.

JSON Decoding

In order to decode the JSON response into the Lecture class, we can create an instance of JSONDecoder, and then call the decode method to convert it from a JSON to a Lecture object.

// Assume we already received the data from an Alamofire call, stored in a variable named responseData

// Create an instance of a JSONDecoder
let jsonDecoder = JSONDecoder()

// Decode the data as a Lecture (optional type)
let lecture = try? jsonDecoder.decode(Lecture.self, from: responseData)

Note: Since the decoding can throw errors, we precede the decode method by a try?, which will make the lecture constant an optional value. This means that lecture will either contain a Lecture item or it could be nil.

Using Your Decoded Data: @escaping completions

Once you’ve decoded the lecture JSON as a Lecture item, how do you use it? Since you're downloading this JSON from the Internet, it might take forever to load, and you only want to display this information on the screen once it’s ready (and possibly use a loading indicator in the meantime as the information loads). We also don’t want our application to just stall and wait until we get a response to do something else.

This is where a completion handler comes in handy. A completion handler is an argument passed into whatever networking function you make.

The completion handler will:

  1. Take in your decoded object(s)

  2. Pass them to the ViewController (or wherever you call this networking function from)

  3. Run some block of code that you write (ex: reload the view controller with the fetched data on the screen)

Back to the Lecture class example from before. Suppose we want to make a function that makes the network call and returns to us the Lecture JSON decoded as a Lecture item. Here’s an example of what that would look like:

static func getLecture(completion: @escaping (Lecture) -> Void) {
  Alamofire.request(endpoint, method: .get).validate().responseData { response in
    switch response.result {
    case let .success(data):
      let jsonDecoder = JSONDecoder()
      
      if let lecture = try? jsonDecoder.decode(Lecture.self, from: data) {
        /* Here we're calling the completion function 
        (passed in as an argument to the network call) 
        on the lecture object we just decoded */
        completion(lecture)
      }
    case .failure(let error):
      print(error.localizedDescription)
    }
  }
}

This completion handler is marked as @escaping because it can "escape" from the networking function call itself and hand it off to whoever called the function -- it doesn’t need to go through every line inside the function. Don't worry too much about what this means though!

Here, you can see that completion is a function that takes in one argument which is of type Lecture and has a return type of Void. We only call completion when we know that the response succeeded and we actually have an actual Lecture object.

Also note that here, we are using the responseData method instead of the responseJSON method! This is important because responseData converts the response into a Data object which is what JSONDecoder needs to decode it.

Making Network Calls

It's good practice to create a NetworkManager class with static methods for every network call your app makes, with the endpoint URLs hidden to the public (in case it’s private information!). This means that the earlier getLecture function in the previous code snippet would be inside a class named NetworkManager.

Then, in your ViewController (or wherever you want to make a network call from), you can make a network request and handle it appropriately. For example, if you have a variable for lecture and need to reload the data of a table view after you get lecture data from the Internet, you would do it like so:

// In a view controller that has a lecture variable and tableView variable
func getClasses() {
  // Call the getLecture method in your NetworkManager
  NetworkManager.getLecture { lecture in
    self.lecture = lecture
    
    // We want to run any UI updates inside a DispatchQueue.main.async {...} block
    DispatchQueue.main.async {
      self.tableView.reloadData()
    }
  }
}

The { lecture in ... } is the escaping completion we defined in the NetworkManager earlier. In this case, we get the lecture variable only if we made the network call to the endpoint, received a success, received the JSON, and then decoded it. If (and only if) the network call succeeded, we run completion (the block of code from lines 5 through 10). This ensures that we receive the updated Lecture object only when it’s ready, so the screen isn’t frozen while the app is trying to execute all of these network request steps.

PDF Download

Last updated