Puttting it all together
In the next and final step, we're building the actual type that will be used as the proverbial type eraser. Just as before, lets have a look at the code first:
final class AnyComputer<Processor: CPU>: Computer {
private let box: AnyComputerBase<Processor>
var processor: Processor {
return box.processor
}
var processorCount: Int {
return box.processorCount
}
init<Concrete: Computer>(_ computer: Concrete)
where Concrete.ProcessorType == Processor {
box = AnyComputerBox(computer)
}
}
This AnyComputer
conforms to the Computer
protocol and is generic
over the CPU
type that the protocol requires. Once again, we implement
the protocol requirements (processor
, and processorCount
) and
forward to a boxed type. This time we're forwarding to
private let box: AnyComputerBase<Processor>
. This box
is set in the
initializer where most of the magic happens:
init<Concrete: Computer>(_ computer: Concrete)
where Concrete.ProcessorType == Processor {
box = AnyComputerBox(computer)
}
The problem with protocols with associated types
is that you can't
use them as property types. Here, init
requires any type conforming to
the Computer
protocol. This is done by having a method-generic type
Concrete
that requires Computer
conformance. Even more, we also add
a constraint that makes sure that the generic Processor
type of the
new AnyComputer
class is the same type as the associated type
of the
Concrete
Computer
type.
And now comes the kicker: Since we cannot set a property as being of
type Computer
we, instead, have a property that is of
AnyComputerBase
with a generic type for the Processor
. As our
AnyComputerBox
type is a subclass of AnyComputerBase
we can
literally put any box (that is a subclass of AnyComputerBase
into
this property. In this case, we're creating a new box with the
Concrete
Computer
.
Then we return the implementations of the contents of the box (i.e. the
actual Concrete
Computer
) in our Computer
implementations:
var processorCount: Int {
return box.processorCount
}
Using It
With all this machinery in place, we can finally use this in order to have different types (which share an associated type) in one container:
let powerComputers: [AnyComputer<PowerPC>] =
[AnyComputer(PowerMacG5()), AnyComputer(Xbox360())]