Skip to content

Type Erasure ​

Type erasure hides a protocol's generic or associated type behind a concrete wrapper. It is useful when a collection must hold values whose underlying generic types differ.

When it helps in iOS ​

A dashboard has multiple card providers, each producing a different model type. The screen needs a uniform list of display titles without exposing every provider type.

Original example: erase card providers ​

swift
import Foundation

protocol CardProvider {
    associatedtype Card
    func load() -> Card
    func title(for card: Card) -> String
}

struct AnyCardProvider {
    private let loadTitle: () -> String

    init<P: CardProvider>(_ provider: P) {
        loadTitle = { provider.title(for: provider.load()) }
    }

    func title() -> String { loadTitle() }
}

struct WeatherProvider: CardProvider {
    func load() -> Int { 24 }
    func title(for card: Int) -> String { "Temperature: \(card)°" }
}

struct CalendarProvider: CardProvider {
    func load() -> Date { Date() }
    func title(for card: Date) -> String { "Next event: \(card.formatted(date: .abbreviated, time: .omitted))" }
}

let providers: [AnyCardProvider] = [
    AnyCardProvider(WeatherProvider()), AnyCardProvider(CalendarProvider())
]
let titles = providers.map { $0.title() }

Each wrapper captures the operations needed by the dashboard. The array has one concrete element type, even though the providers' Card types differ.

Production decisions ​

Erase only the capabilities the caller needs; a wrapper that forwards dozens of methods is hard to maintain. Use generics when a feature has one provider type throughout. SwiftUI's AnyView is another form of type erasure, but prefer some View or @ViewBuilder when possible to preserve static view identity.

Why the wrapper is necessary ​

CardProvider has an associated Card type. WeatherProvider uses Int, while CalendarProvider uses Date, so a plain collection of concrete providers cannot have one element type without hiding that difference. AnyCardProvider captures the only operation the dashboard needs—load and format a title—in a closure. The array stores wrappers of one concrete type. The underlying model type stays known inside each captured closure and disappears from the dashboard API.

From example to a dashboard ​

The app assembles card providers according to account features or user customization, wraps them, and gives a list to the dashboard view model. Each card can supply a title without the view switching on provider type. If loading becomes asynchronous, update the wrapper's closure to async throws and define cancellation and error presentation. Preserve stable card IDs separately so SwiftUI or diffable data sources do not lose identity when providers are rebuilt.

Failure modes and verification ​

Test both provider types through the wrapper and ensure captured providers have the intended lifetime. Type erasure can hide useful type information; do not erase early when a generic view can keep the associated type. Avoid AnyView merely to combine branches in SwiftUI when @ViewBuilder can preserve static structure. Use a wrapper only for the capabilities the consumer actually needs.

All patterns

Released under the MIT License.