Swift World: Type Erasure
Swift is a Protocol-Oriented Programming Language. — WWDC 2015 Protocol-Oriented Programming in Swift
Generics are one of the most powerful features of Swift, and much of the Swift standard library is built with generic code. — The Swift Programming Language
I start this article with two quotes on protocol and generics. Both are core features of Swift. As we know, there are generics for class, struct and enum. What about protocol?
Suppose we’re working on a cache framework for our app. For different data type being cached, we often make them conform to an interface as following Cachable protocol.
protocol Cachable<T> {}
We will get “error: protocols do not allow generic parameters; use associated types instead”. This error message leads us to associated types.
An associated type gives a placeholder name to a type that is used as part of the protocol. — The Swift Programming Language
Ok, let’s follow the suggestion to use associated type. The following codes are from an open source cache framework — hyperoslo/Cache.
protocol Cachable {
associatedtype CacheType func decode(_ data: Data) -> CacheType?
func encode() -> Data?
}
But we all know Swift is strongly typed. So
The actual type to use for that associated type isn’t specified until the protocol is adopted. — The Swift Programming Language
When a type conforms to the protocol with associatedtype, it needs to make associatedtype clear. As shown in the following snippet, the associatedtype is defined as String by typealias CacheType = String
.
extension String: Cachable { typealias CacheType = String func decode(_ data: Data) -> CacheType? {
guard let string = String(data: data, encoding: String.Encoding.utf8) else {
return nil
} return string
} func encode() -> Data? {
return data(using: String.Encoding.utf8)
}
}
This is protocol’s own mechanism for generics — Protocol with Associated Types (PATs). @NatashaTheRobot’s article Swift: What are Protocols with Associated Types? is a simple and clear explanation. Dive in Alexis Gallagher’s talk if you want to get deep and complete understanding on PATs.
The world seems perfect because finally we have generics for protocol. But in following cases, PATs have troubles. Suppose we have CacheItem which wraps a Cachable item and its expiry date.
struct CacheItem {
let item: Cachable
let expiryDate: Date
...
}
We will get error as below.
error: protocol 'Cachable' can only be used as a generic constraint because it has Self or associated type requirements
Or we will get same error when we want to define an array for Cachable.
let cache: [Cachable]
Swift’s code base is a big treasure where we can find solution for these cases. The technique is type erasure. One of the examples is AnySequence which is type-erased wrapper for Sequence. Its internal mechanism is described as following quote.
An instance of AnySequence forwards its operations to an underlying base sequence having the same Element type, hiding the specifics of the underlying sequence. — Swift Standard Library API Reference
So we need AnyCachable for Cachable.
class AnyCachable<T>: Cachable {
private let _decode: (_ data: Data) -> T?
private let _encode: () -> Data? init<U: Cachable>(_ cachable: U) where U.CacheType == T {
_decode = cachable.decode
_encode = cachable.encode
} func decode(_ data: Data) -> T? {
return _decode(data)
} func encode() -> Data? {
return _encode()
}
}
The main points are:
- AnyCachable is a generic type conforming to Cachable.
- The type parameter T is also Cachable’s associated type.
- Define internal closures for functions and properties of the protocol Cachable.
- Forward the operations to the wrapped Cachable instance.
Then we can solve the problems as shown below.
struct CacheItem {
let item: AnyCachable<Any>
let expiryDate: Date
...
}let cache: [AnyCachable<String>] = [AnyCachable("Hello"), AnyCachable("World")]
There are other implementations for type erasure like Robert Edwards described in Breaking Down Type Erasure in Swift.
Finally, I will recommend other resources on type erasure.
- Keep Calm and Type Erase On by Gwendolyn Weston
- Swift: Attempting to Understand Type Erasure by NatashaTheRobot
- Type erasure using closures in Swift by John Sundell
- A Little Respect for AnySequence by Rob Napier
Thanks for your time.