Link Search Menu Expand Document

Introduction to Web APIs and Firebase

Michael Lin

Table of Contents

  1. Swift Continued
    1. Escaping Closures
    2. Capturing and Reference Cycle
  2. APIs
    1. Introduction
    2. Web APIs
    3. Firebase

Swift Continued

Escaping Closures

When you define a closure in Swift, the information that you will use inside the closure usually comes from two places: the parameters which are the values that were passed in, plus the context which is all the variables accessible to you when the closure is defined.

var sum = 0
[1, 2, 3, 4].forEach { num in sum += num }

In the example above, num is a parameter and sum is a piece of the context.

This allows us to carry out much more complex operations than would’ve been able to if closures can only use what’s passed in as parameters.

However, the mechanism introduces some interesting challenges when a closure needs to outlive the scope where it was defined. A common use case for that is completion. We learned about completion last lesson as a way of adding “after effect” to a asynchronous block, such as putting our asynchronously loaded image into self.imageView.

Despite our last example, completion closures appear much more frequently as the argument of a function rather than a local variable, such as UIView.animate(withDuration:animation:completion:) or URLSession.shared.dataTask(with:completionHandler:).

The closures associated with these asynchronous completions live in a special category. They are called escaping closures. Specifically, an escaping closure is a closure that’s called after the function it passes to returns. For instance, since functions such as UIView.animate are asynchronous, the completion can be called long after the original function returns. In short, an escaping closure outlives the function it was passed to.

Closures are non-escaping by default, unless they are declared as @escaping in the function’s definition

func loadUser(with uid: String, completion: @escaping (User)->Void) {}

or if the closure is optional

func loadUser(with uid: String, completion: ((User)->Void)? = nil) {}

Optional closures in parameters are escaping by default.

Capturing and Reference Cycle

For the most part, or at least in the scope of this training program, you probably won’t find yourself in the need of using the @escaping syntax in your function signature. However, you will have to deal with functions that takes an escaping closure as a parameter, and this is when you need to be careful about the retain cycle caused by strong capturing the these escaping closures.

Since arguments of a function only exist during the function call, and instance properties only live as far as the instance goes, if we decide to use these variables in an escaping closure, we need a way of guaranteeing that they will still be there when the closure is called. Therefore, by default, in an escaping closure, a set strong references to these variables are maintained. This is known as capturing.

A escaping closure implicitly captures the context that is used inside the closure so that when the closure is evoked, it has the references it needs to finish the execution.

{ image in
    self.imageView.image = image
}

Here, self is implicitly captured with the closure to ensure that we can get to self.imageView as long as the closure exists.

You can made this behavior explicit by defining a capture list. It’s a list of objects you wish to be captured from the current context.

{ [self] image in
    self.imageView.image = image
}

But because self is captured with a strong reference, it will not be deallocated by the ARC so long as our closure exist, which, in a certain scenario, can create a reference cycle. This video will show you an example of that using a Timer instance inside a view controller.

APIs

Introduction

So far, we’ve been mostly relying on local data to power our app. From the members’ pictures to all the pokemon stats, this information is shipped with the app’s bundle which users get when they download the app. However, there’s only so much we can store on an iPhone, and in some cases we might want to dynamically fetch data from other sources or even other users.

This is why apps are often backed by a server, or a “backend”. For those who may be a bit unfamiliar with the term, they are usually just a humble computer running somewhere in the world that’s in charge of receiving and dispatching data coming from all the users (the clients).

Naturally, between the server and its clients, there ought to be some protocols for transferring the data. This again will be another huge topic that you will need to take a class for (CS 168). But for our purpose in the app development world, they will be the APIs. APIs come in many different forms but today we will introduce two of them: Web APIs and Firebase.

Web APIs

Web APIs are one of the most common types of API out there. An example would be fetching the image of Pokemon in your project two. There, you are sending an HTTP GET request to the server. If nothing goes wrong, the server will give you an HTTP response object with the binary of the image.

An HTTP request can be broken down to several components:

  • URL: Specify where the data is located
  • Request Method (GET, POST, etc.): Specify how you will get the data
  • Request Header: Provide the request context such as user agent (UA)
  • Optional Request Body: Can be JSON, XML, plain text, etc

In response, the server will send an HTTP response that has

  • Status Code, some examples:
    • 200: Status OK
    • 301: Moved permanently
    • 404: Error not found
    • 400: Bad request
    • 502: Internal Server Error
  • Response Headers: Metadata about the body
  • Response Body: The response payload, can be JSON, XML, Plain Text, etc

One of the benefits of using web API is its accessibility. Modern programming languages typically offer easy-to-use libraries for performing HTTP requests (requests package in Python, fetch() in JavaScript, java.net.HttpUrlConnection in JDK). In iOS, this can be implemented with URLSession.

guard let url = URL(string: "https://img.pokemondb.net/sprites/omega-ruby-alpha-sapphire/dex/normal/shroomish.png") else { return }

URLSession.shared.dataTask(with: url) { data, response, error in
    guard error != nil else { return }
    DispatchQueue.main.async {
        if let data = data {
            if let image = UIImage(data: data) {
                self.image = image
            }
        }
    }
}

Firebase