2️⃣Callbacks

Fall 2023 | Vin Bui

We learned that we can use an HTTP request to retrieve information from a server. We also learned that the server responds with a status code and some data represented by JSON. Now, how do we make these API calls in Swift?

Understanding Network Calls

Before we look at Swift code, let’s try to understand how a networking call works in the perspective of the client. Grab a phone and open Instagram or any social media app. When we launch the app, not all of the information is fetched from the backend right away. If we had a slower internet connection, these posts and stories will take even longer to load. Now notice how the app does not freeze even though we are sending network requests to the backend server. What’s really happening is that these network calls are happening asynchronously. In other words, it is running in the background.

Why is this necessary? A client’s internet connection can vary and we do not know exactly how long it will take to receive a response back from the server. Because of this, we do not want our app to freeze while we are fetching information from the backend. Additionally, we want to be notified as soon as the server sends a response back so that we can perform any UI updates. In the case of Instagram, once the client receives these posts on their feed from the server, the client is notified and the app automatically updates the UI. There are many ways we can handle asynchronous code in Swift. In this course, we will be using a traditional approach using callbacks (completion handlers).

Callbacks Explained

A callback is a function that is passed into another function as an argument and is called after the original function completes. In Swift, these callback functions are known as completion handlers. Imaging this scenario:

You are expecting a package delivered to your house containing a gift for your parents. Unfortunately, you are out of town and will not be able to deliver the gift yourself. You ask your friend that lives in your house to give it to your parents once the package arrives. Your friend could do two things:

  1. Stand at the front door and wait for the package for days without doing anything else, then deliver it to your parents

  2. Have common sense and go on with life while they wait for the package to arrive, and then deliver it once it arrives

In this example, the package delivery is the network request. Your friend delivering it to your parents once the package arrives is the callback. You are the original function that requested this callback function to do something. Another common real-life example would be asking someone to call you back once they are ready to call you back instead of staying on the line.

Writing Callbacks

Consider a social media app similar to Instagram. We have a Post object representing a post. When we open our feed page, we send a request to our backend server to fetch new posts. Our function header to fetch our feed could look something like this:

func fetchFeed(completion: @escaping ([Post]) -> Void) { }

We create a function called fetchFeed that takes in another function as an argument (the callback) with the name completion. We can name this callback function to whatever we want, but make sure it is clear that it is a completion handler. So this completion parameter needs to have a type, as with any parameter inside of a function header. Well, we want this parameter to be a function type in order for it to be a callback. When we declare a function type, we need to specify the types of the function’s parameters as well as the function’s return type. Remember, the purpose of this callback function is to deliver the information from the backend, so its parameter type will be an array of Post objects. This callback function receives these posts as an argument but does not return anything, hence the return type of Void.

Just to bring everything together, we declared a function as a parameter inside of fetchFeed called completion whose type is ([Post]) -> Void meaning it takes in one argument which is an array of Post objects and does not return anything.

You may have noticed the keyword @escaping. In the example earlier, when I asked my friend to deliver the gift to my parents, I am the original function. During that original function call, I asked my friend (the callback) to complete this task for me, but only want to ask them once and forget about it for now. We can think of the original function call being erased here. However, my friend still has a task to do, and I do not want them to forget (be erased). Connecting this back to Swift, we mark these callback functions (completion handlers) with the @escaping keyword so that it can outlive the original function. Otherwise, as soon as the original function is complete, the callback will be erased as well and we do not want that!

The complete network call could look something like this:

func fetchFeed(completion: @escaping ([Post]) -> Void) {
    var posts: [Post] // The list of posts
    // 1. Send a request to the backend (ex: GET request)
    // 2. Handle the backend response (error handling, decode JSON data)
    // 3. Assign the variable `posts` with the decoded data
    completion(posts) // 4. Give the callback function the list of posts
}

Last updated