The final step in our closure-based type erasure is to implement the initializer that will be used to wrap Cows and Goats:
final class AnyAnimal<T>: Animal where T: Food {
typealias FoodType = T
private let eatBlock: (T) -> Void
init<A: Animal>(animal: A) where A.FoodType == T {
eatBlock = animal.eat
}
func eat(food: T) {
eatBlock(food)
}
}
Let's sum up the changes:
- We marked the class final to avoid any wrong usage.
- We added an eatBlock: (T) -> Void property to capture the eat method from the animal.
- We added an implementation to func eat(food: T) to forward the calls to the original method.
- We added the generic initializer that also binds the T type to FoodType of the provided Animal type, A.FoodType.
Let's now gather the flock of grass eaters and make let them graze freely in the fields:
let aCow = AnyAnimal(animal: Cow())
let aGoat = AnyAnimal(animal: Goat())
let grassEaters = [aCow, aGoat] // it's a small flock but a significant one
assert(grassEaters is [AnyAnimal<Grass>]) // Yay! All grass eaters
grassEaters.forEach { (animal) in // They are quire hungry let them eat
animal.eat(food: Grass())
}
// Output:
Grass is yummy! moooooo!
Grass is good! meehhhh!
This concludes the implementation of the closure based type erasure. Now, let's take a complete look at the box-based type erasure. While more verbose, this implementation should not scare you away.