Generics in Swift allow you to write flexible and reusable code. However, they come with a cost: sometimes they can make your code more complex and harder to read, especially when you need to work with protocols that include associated types or self requirements.
This is where type erasure becomes invaluable. It simplifies the use of generics by wrapping concrete types into a single, consistent interface. This tutorial will walk you through the concept of type erasure, when to use it, and how to implement it in Swift.
Type erasure is a programming technique that hides the underlying type information of a concrete type behind a protocol or abstract type. This enables you to work with multiple concrete types that conform to a protocol in a unified way.
For example, Swift’s standard library uses type erasure in the AnySequence
and AnyPublisher
types. These types wrap concrete implementations of sequences and publishers, allowing you to work with them abstractly.
There are several scenarios where type erasure is useful:
Let’s walk through a practical example. Suppose you have a protocol:
protocol Shape {
func area() -> Double
}
And two conforming types:
struct Circle: Shape {
let radius: Double
func area() -> Double {
return .pi * radius * radius
}
}
struct Rectangle: Shape {
let width: Double
let height: Double
func area() -> Double {
return width * height
}
}
You want to store an array of Shape
objects, but Shape
alone cannot be used as a type because it doesn’t conform to itself. Let’s use type erasure to solve this.
Define a concrete type that conforms to the Shape
protocol but wraps any type that also conforms to Shape
.
class AnyShape: Shape {
private let _area: () -> Double
init<T: Shape>(_ shape: T) {
self._area = shape.area
}
func area() -> Double {
return _area()
}
}
Here’s what’s happening:
Shape
.area
is captured in a closure (_area
) that is stored internally.Now, you can use AnyShape
to store different shapes in a collection:
let shapes: [AnyShape] = [
AnyShape(Circle(radius: 5)),
AnyShape(Rectangle(width: 10, height: 4))
]
for shape in shapes {
print("Area: \(shape.area())")
}
// Area: 78.53981633974483
// Area: 40.0
Let’s enhance this example by adding more functionality, like perimeter calculations. You can expand the protocol and the AnyShape
class to handle additional methods or properties.
import SwiftUI
let views: [AnyView] = [
AnyView(Text("Hello")),
AnyView(Image(systemName: "star"))
]
Type erasure is a powerful tool in Swift that enhances code flexibility and simplifies working with generics and protocols. While it’s not always the best solution for every problem, understanding when and how to use it can significantly improve your code design.