iOS Interview - Understanding Type Erasure in Swift: A Comprehensive Guide

April 09, 20245 min read#swift, #ios, #interview

Introduction

When working with protocols and generics in Swift, you may encounter situations where you need to hide the specific type information of an object and provide a more generic interface. This is where type erasure comes into play. In this blog post, we’ll dive deep into the concept of type erasure in Swift, explore its benefits, and walk through a practical code example to illustrate how it works.

If you are familar with Combine framework, you will see types like AnyPublisher, or method like eraseToAnyPublisher which wraps Publisher types and return AnyPublisher to hide the underlying type information. This is a pratical of the type earsure mechanism.

What is Type Erasure?

Type erasure is a powerful technique in Swift that allows you to conceal the specific type information of an object, presenting a more generic interface to the outside world. By erasing the specific type, you can work with objects in a more flexible and abstract manner, enhancing code reusability and modularity.

In Swift, type erasure is typically achieved using the Any or AnyObject types.

  • Any can hold a reference to any type
  • AnyObject can hold a reference to any class type.

By utilizing these types, you can erase the specific type information and interact with objects through a generic interface.

Another technique to erase types is to create a concrete wrapper type (such as AnyPublisher), which hide away the details of the generic types.

Practical Example:

Let’s consider a practical example to demonstrate how type erasure works in Swift. Suppose we have an Animal protocol that defines a name property and a makeSound() method:

protocol AnimalSound {
    func play()
}

struct DefaultDogSound: AnimalSound {
    func play() {
        print("Woof!")
    }
}

struct DefaultCatSound: AnimalSound {
    func play() {
        print("Meow!")
    }
}

protocol Animal {
    associatedtype Sound
    var name: String { get }
    func makeSound() -> Sound
}

We can create concrete implementations of the Animal protocol, such as Dog and Cat classes:

class Dog: Animal {
    var name: String

    init(name: String) {
        self.name = name
    }

    func makeSound() -> AnimalSound {
        DefaultDogSound()
    }
}

class Cat: Animal {
    var name: String

    init(name: String) {
        self.name = name
    }

    func makeSound() -> AnimalSound {
        DefaultCatSound()
    }
}

Now, let’s say we want to work with a collection of animals without exposing their specific types. This is where type erasure comes in handy. We can create a type eraser struct called AnyAnimal that conforms to the Animal protocol:

struct AnyAnimal: Animal {
    private let _name: () -> String
    private let _makeSound: () -> AnimalSound

    init<T: Animal>(_ animal: T) where T.Sound == AnimalSound {
        _name = { animal.name }
        _makeSound = { animal.makeSound() }
    }

    var name: String {
        return _name()
    }

    func makeSound() -> AnimalSound {
        _makeSound()
    }
}

Inside AnyAnimal, we have private properties _name and _makeSound of function types that capture the name and makeSound() implementations of the underlying animal. The init method takes a generic parameter T that conforms to the Animal protocol and initializes the private properties with the corresponding implementations.

Using AnyAnimal, we can create an array of animals without exposing their specific types:

let dog = Dog(name: "Buddy")
let cat = Cat(name: "Whiskers")

let animals: [AnyAnimal] = [AnyAnimal(dog), AnyAnimal(cat)]

We can then iterate over the animals array and call the makeSound() method on each animal, invoking the underlying implementation based on the specific animal type:

for animal in animals {
    print("\(animal.name) says:")
    let sound = animal.makeSound()
    sound.play()
}

By using AnyAnimal as the type eraser, we can work with different types of animals in a generic manner without exposing their specific types. The specific type information is erased, and we only interact with the Animal protocol through the AnyAnimal struct.

In the console, you will see the following as expected:

Buddy says:
Woof!
Whiskers says:
Meow!

Benefits of Type Erasure:

Type erasure offers several benefits in Swift programming:

  • Flexibility: Type erasure allows you to work with objects in a more flexible manner by hiding their specific types and providing a generic interface.

  • Abstraction: By erasing the specific types, you can achieve a higher level of abstraction in your code, making it more modular and reusable.

  • Protocol Conformance: Type erasure enables you to create collections or arrays of objects that conform to a specific protocol

Quick Drop logo

Profile picture

Personal blog by An Tran. I'm focusing on creating useful apps.
#Swift #Kotlin #Mobile #MachineLearning #Minimalist


© An Tran - 2024