Protocols, Existentials, and Composition (with version cues)#
Practical patterns for using protocols, any vs some, and type erasure.
Version Quick Reference#
- Swift 5.6+:
any keyword required for existential types. - Swift 5.7: primary associated types improve existential ergonomics; better diagnostics for
Self and associated-type constraints. - Swift 6+: stricter
Sendable/actor-isolation checks make any vs some choices more visible.
any vs some#
any Protocol: type-erased box; dynamic dispatch; multiple conformers can coexist (heterogeneous containers, stored properties).some Protocol: concrete type stays hidden but fixed per function; static dispatch; great for return types when a single conformer is guaranteed.
1
2
| func makeRow() -> some View { Text("Hi") } // fixed concrete type
let rows: [any View] = [Text("Hi"), Image(systemName: "star")] // heterogeneous
|
Self Requirements and Associated Types#
- Protocol requirements mentioning
Self or an associated type block plain existential use. Reach for:- Generics on the function/type (
func render<P: View>(_ view: P) -> some View) - Type erasure wrappers (e.g.,
AnySequence, AnyPublisher) - Opaque returns (
some) when a single conformer is stable.
Type Erasure Pattern#
- Wrap a protocol with associated types in a box to regain existential ergonomics.
1
2
3
4
5
6
7
8
9
10
11
12
| protocol DataProvider {
associatedtype Item
func items() -> [Item]
}
struct AnyDataProvider<Item>: DataProvider {
private let _items: () -> [Item]
init<P: DataProvider>(_ provider: P) where P.Item == Item { _items = provider.items }
func items() -> [Item] { _items() }
}
let providers: [AnyDataProvider<String>] = [AnyDataProvider(LocalProvider())]
|
Protocol Composition#
- Combine requirements with
&: any A & B. - For
some, composition still promises one concrete type that conforms to all listed protocols.
1
2
3
4
| protocol Displayable { var title: String { get } }
protocol Loadable { func load() async throws }
func makeFeature() -> some Displayable & Loadable { Feature() }
|
Extensions vs Inheritance#
- Prefer protocol extensions to add shared behavior without forcing inheritance. Keep inheritance shallow; compose small protocols instead.
- Avoid over-broad “god protocols.” Split into role-focused protocols and recompose at use sites.
Testing and Diagnostics#
- When using type erasure, add small tests to ensure forwarding works (
items() returns expected values). - If you need compile-time coverage, add helper functions in tests that must type-check to catch regressions in constraints.