Working with the API calls in the playgrounds

Photo by JJ Ying / Unsplash

Making the network requests is the true core functionality of many applications. Logging in, getting the data, making changes, and many, many more.

Implementing these calls would be a pain if not the contracts we have that determine how the network calls works, what they require and what they provide. It's great that they always work and they don't have any bugs. The only thing we need to do is to implement the handling on the application side and we are done.

In an ideal world that is.

In the real world, it's located somewhere between this ideal case and:

Oh man, we have no documentation and nothing is working

Handling API calls tend to be time-consuming and annoying. In addition, the only way to test the code is to run the application. One of my personal workarounds was to write unit tests without mocks during the development. When I was running them they were contacting the API and I was able to see what is going on without running the application. In the end, I was either mocking them or discarding/disabling. It wasn't ideal but much better than the regular approach.

Workarounds are not needed any more thanks to the playgrounds.

Let's imagine a situation where we need to implement an API call that will return a list of items. We will use https://api.publicapis.org/entries as an example. To make it harder and realistic at the same time we have the URL and no documentation.

First, we need to create a playground. In Xcode click on File > New > Playground or use shift + option + command + N. Then select Blank playground from the list.

Now we need to add a small tweak to make it asynchronous-execution friendly:

import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
By default, all top-level code is executed, and then execution is terminated. When working with asynchronous code, enable indefinite execution to allow execution to continue after the end of the playground’s top-level code is reached. This, in turn, gives threads and callbacks time to execute.

The Documentation

Then we need to make the call to see what is there. We could use the browser but where is fun in that?

let apiListRequestUrl = URL(string: "https://api.publicapis.org/entries")!
let apiListTask = URLSession.shared.dataTask(with: apiListRequestUrl) { data, response, error in
    guard let data = data else { return }
    let jsonString = String(data: data, encoding: .utf8)!
    print(jsonString)
}

The code above makes the request to a specified URL and provides data or error in return.

When we run the playground we see:

What is great is that all the features we used in Working with the Vision framework in the playgrounds are there.

This means we can tap on the eye button:

And we can do the same with the rectangle / embed button:

Regular prints are working as well:

{"count":1185,"entries":[{"API":"Axolotl","Description":"Collection of axolotl pictures and facts","Auth":"","HTTPS":true,"Cors":"yes","Link":"https://theaxolotlapi.netlify.app/","Category":"Animals"},{"API":"Cat Facts","Description":"Daily cat facts","Auth":"","HTTPS":true,"Cors":"no","Link":"https://alexwohlbruck.github.io/cat-facts/","Category":"Animals"},{"API":"Cataas","Description":"Cat as a service (cats pictures and gifs)","Auth":"","HTTPS":true,"Cors":"no","Link":"https://cataas.com/","Category":"Animals"},{"API":"catAPI","Description":"Random pictures of cats","Auth":"","HTTPS":true,"Cors":"yes","Link":"https://thatcopy.pw/catapi","Category":"Animals"},{"API":"Cats","Description":"Pictures of cats from Tumblr","Auth":"apiKey","HTTPS":true,"Cors":"no","Link":"https://docs.thecatapi.com/","Category":"Animals"},{"API":"Dog Facts","Description":"Random dog facts","Auth":"","HTTPS":true,"Cors":"yes","Link":"https://dukengn.github.io/Dog-facts-API/","Category":"Animals"},\
[...]

Based on the gathered info we can make a few assumptions. The results are in a container that has two main variables count and entries:

{"count":1185,
 "entries":[\* Entry items *\]

We can start implementing the simple structure:

struct ApiListEnvelope: Codable {
    let count: Int
}

And add a code for decoding instead of print:

let decoded = try! JSONDecoder().decode(ApiListEnvelope.self, from: data)

What happens is:

This confirms that the count is properly decoded but this is not the end. We need an Entry:

{"API":"Axolotl","Description":"Collection of axolotl pictures and facts","Auth":"","HTTPS":true,"Cors":"yes","Link":"https://theaxolotlapi.netlify.app/","Category":"Animals",
struct Entry: Codable {
    let api: String
    let description: String
    let link: String
    let category: String
}

To make the envelope complete:

struct ApiListEnvelope: Codable {
    let count: Int
    let entries: [Entry]
}

But when we run the playground we see there is a problem:

__lldb_expr_34/TheAPIPlayground.playground:30: Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "api", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "entries", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"api\", intValue: nil) (\"api\").", underlyingError: nil))

The names in JSON doesn't match which means we need to provide CodingKeys for Entry:

enum CodingKeys: String, CodingKey {
    case api = "API"
    case description = "Description"
    case link = "Link"
    case category = "Category"
}

Now our response is decoded properly. Note: we decode only the information we intend to use:

This is it. We implemented the loading of the list of items we want to display in the application.

We investigated the structure of the call and implemented decoding step-by-step fixing issues as soon as they arise. Lightning-fast feedback included.

We didn't need to run the application to achieve this. We didn't have to tackle all the issues at once.

This is the full code:

import UIKit
import PlaygroundSupport

struct Entry: Codable {
    let api: String
    let description: String
    let link: String
    let category: String
    
    enum CodingKeys: String, CodingKey {
        case api = "API"
        case description = "Description"
        case link = "Link"
        case category = "Category"
    }
}

struct ApiListEnvelope: Codable {
    let count: Int
    let entries: [Entry]
}

let apiListRequestUrl = URL(string: "https://api.publicapis.org/entries")!
let apiListTask = URLSession.shared.dataTask(with: apiListRequestUrl) { data, response, error in
    guard let data = data else { return }
    let decoded = try! JSONDecoder().decode(ApiListEnvelope.self, from: data)
}

apiListTask.resume()

PlaygroundPage.current.needsIndefiniteExecution = true

⚠️ If you are using M1 and run Xcode using Rosetta playgrounds won't work:

If you have any feedback, or just want to say hi, you are more than welcome to write me an e-mail or tweet to @tustanowskik

If you want to be up to date and always be first to know what I'm working on tap follow @tustanowskik on Twitter

Thank you for reading!

This article was featured in Awesome Swift Weekly #281 🎉

If you want to help me stay on my feet during the night when I'm working on my blog - now you can:

Kamil Tustanowski is iOS Dev, blog writer, seeker of new ways of human-machine interaction
Hey 👋If you are seeing this page it means you either read my blog https://cornerbit.tech or play with my code on GitHub https://github.com/ktustanowski.Thank you...
Kamil Tustanowski

Kamil Tustanowski

I'm an iOS developer dinosaur who remembers times when Objective-C was "the only way", we did memory management by hand and whole iPhones were smaller than screens in current models.
Poland