top of page

Swift Study Notes

💡 Remember This

Boxed protocol types store a concrete value inside a container while exposing only the protocol's interface.

Existential types are Swift's name for boxed protocol values whose concrete type has been erased from the surrounding code.

Opaque protocol types hide a concrete type from the caller while still preserving that exact concrete type throughout the program.

What are Boxed Protocol Types?

A boxed protocol type is a value whose concrete type has been hidden behind a protocol.

Although the value still has a concrete type such as Dog or Cat, Swift stores that value inside a container so that surrounding code interacts only with the protocol's interface.

This allows completely different concrete types to be treated in exactly the same way.

Concrete Types

Before understanding boxed protocol types, it is important to understand concrete types.

A concrete type is a type whose exact identity is known by the compiler.

struct Dog {
    let name: String

    func bark() {
        print("Woof!")
    }
}

Here, the compiler knows everything about Dog.

It knows:

  • Its stored properties.
  • Its memory layout.
  • Its methods.
  • Its size.
  • How instances are created.

When you create an instance, Swift knows exactly what it is working with.

let dog = Dog(name: "Milo")

Nothing has been hidden.

Introducing Protocols

A protocol describes behaviour without describing how that behaviour is implemented.

protocol Animal {
    var name: String { get }

    func makeSound()
}

The protocol says every animal has a name and can make a sound.

It does not say how.

Different concrete types provide their own implementations.

struct Dog: Animal {
    let name: String

    func makeSound() {
        print("Woof!")
    }
}

struct Cat: Animal {
    let name: String

    func makeSound() {
        print("Meow!")
    }
}

Dog and Cat both conform to Animal.

However, they remain completely different concrete types.

The Problem

Suppose we want to store either a Dog or a Cat inside a single variable.

Both conform to Animal.

However, Dog and Cat have different sizes in memory and different implementations.

Swift therefore cannot simply pretend they are the same type.

Something else is required.

The Box

Instead of storing the protocol itself, Swift creates a container.

This container is commonly referred to as a box.

The box stores the concrete value while exposing only the protocol's interface.

let animal: any Animal = Dog(name: "Milo")

The variable's type is any Animal.

The value inside the box is still a Dog.

The Dog has not disappeared.

Its concrete type has simply been hidden from surrounding code.

Looking Inside the Box

You can think of the box as containing three pieces of information.

  • The concrete value.
  • The concrete type.
  • Information describing how that concrete type satisfies the protocol.

Although the concrete type is hidden from your Swift code, Swift itself still knows exactly what it is.

Otherwise it would not know which implementation of makeSound() should execute.

Calling Methods

Suppose the box currently contains a Dog.

let animal: any Animal = Dog(name: "Milo")

animal.makeSound()

Swift eventually calls:

Dog.makeSound()

If the box instead contains a Cat:

let animal: any Animal = Cat(name: "Luna")

animal.makeSound()

Swift instead calls:

Cat.makeSound()

The code calling makeSound() never needs to know which concrete type is stored inside the box.

Protocol Witness Tables

How does Swift know which implementation should execute?

Each boxed protocol value carries information describing how the concrete type satisfies the protocol.

This information is stored in a structure known as a protocol witness table.

Whenever a protocol requirement is called, Swift consults this information to locate the correct implementation.

If the box contains a Dog, Dog's implementation executes.

If the box contains a Cat, Cat's implementation executes.

Why Use a Box?

The box allows code to work with behaviour rather than concrete types.

Instead of writing separate code for Dog, Cat and every future Animal, code can simply work with Animal.

func describe(_ animal: any Animal) {
    print(animal.name)
    animal.makeSound()
}

The function accepts any boxed value conforming to Animal.

It does not need to know whether the underlying value is a Dog, Cat or another type.

What Has Been Hidden?

The box hides the concrete type.

It does not hide the protocol.

Everything declared by the protocol remains available.

animal.name
animal.makeSound()

Anything that belongs only to Dog is no longer accessible.

animal.fetchStick() // Error

The box exposes only the interface described by Animal.

Important

A boxed protocol value does not remove the concrete type.

It simply hides that type behind the protocol interface.

The concrete value continues to exist inside the box.

Arrays of Boxed Protocol Types

One advantage of boxing is that different concrete types can appear in the same collection.

let animals: [any Animal] = [
    Dog(name: "Milo"),
    Cat(name: "Luna")
]

Each element is a boxed protocol value.

Although every element has the same static type, each box may contain a different concrete type.

Runtime Flexibility

The concrete type stored inside the box can change over time.

var animal: any Animal = Dog(name: "Milo")

animal = Cat(name: "Luna")

The variable always stores an Animal.

However, the concrete type inside the box changes from Dog to Cat.

Interview Questions

What is a boxed protocol type?

A boxed protocol type stores a concrete value inside a container while exposing only the protocol's interface.

Does boxing remove the concrete type?

No.

The concrete type continues to exist inside the box.

Only its identity is hidden from surrounding code.

Why does Swift need a box?

Different concrete types have different memory layouts.

The box provides a common representation that allows values conforming to the same protocol to be treated uniformly.

What information does the box contain?

The box stores the concrete value together with runtime information describing its concrete type and how it satisfies the protocol.

What is a protocol witness table?

A protocol witness table contains the implementations of the protocol requirements for a particular concrete type.

Swift uses it to call the correct methods at runtime.

Final Revision Note

A boxed protocol type allows different concrete types to be treated uniformly without losing their individual implementations.

The concrete type still exists.

Swift simply stores it inside a box and exposes only the protocol's interface.

Remember

A boxed protocol type hides a concrete type behind a protocol, not instead of a protocol.

The concrete value still exists, and Swift still knows exactly what it is.

bottom of page