在Swift中,协议(Protocol)可以拥有关联类型(Associated Type),用于实现类似范型(Generic)的功能。但是,带有关联类型的协议并不是真实类型(Existential),所以无法用作独立的类型声明。
比如——
class C<A, B> {}
var c: C<Int, String>
struct S<A, B> {}
var s: S<Int, String>
都是成立的,但——
protocol P {
associatedtype A
associatedtype B
}
var p: P<A, B>
是无法成立的。
为此,我们需要使用到类型擦除来将一些带有关联类型的协议转化为带有范型的类(Class)或结构体(Struct)。
考虑有一个协议Callable,它带有两个关联类型Input和Output,并且实现了call(Input) -> Output方法——
protocol Callable {
associatedtype Input
associatedtype Output
func call(_ input: Input) -> Output
}
它有一些实现——
struct IncrementByOne : Callable {
func call(_ input: Int) -> Int {
return input + 1
}
}
struct Negation : Callable {
func call(_ input: Int) -> Int {
return -input
}
}
现在,如果我们需要一个变量来存放任意的实现,我们会希望能够书写——
var callable: Callable<Int, Int>
callable = IncrementByOne()
callable.call(0) == 1
callable.call(1) == 2
callable = Negation()
callable.call(0) == 0
callable.call(1) == -1
但是正如之前所说,这样是不可行的。
为此,我们需要一个结构体(或类)AnyCallable,它遵从协议Callable,并且通过范型满足关联类型。我们可以用它转化任意的Callable实例,并与其实现完全相同的功能——
struct AnyCallable<Input, Output> : Callable {
init<Base : Callable>(_ base: Base) where Base.Input == Input, Base.Output == Output {
// FIXME: ...
fatalError()
}
func call(_ input: Input) -> Output {
// FIXME: ...
fatalError()
}
}
由于其中Base也不是真实类型,所以我们不可能将base保存为AnyCallable的成员。但是,我们可以通过闭包(Closure)将base的call方法保存下来——
struct AnyCallable<Input, Output> : Callable {
init<Base : Callable>(_ base: Base) where Base.Input == Input, Base.Output == Output {
_call = base.call
}
private let _call: (Input) -> Output
func call(_ input: Input) -> Output {
return _call(input)
}
}
这样,现在就可以通过——
var callable: AnyCallable<Int, Int>
callable = AnyCallable(IncrementByOne())
callable.call(0) == 1
callable.call(1) == 2
callable = AnyCallable(Negation())
callable.call(0) == 0
callable.call(1) == -1
来实现之前无法实现的逻辑了。
如果一个协议在没有限定为类的情况下拥有可写属性,或者拥有标记为mutating的方法,那它就是可变的。对于这种情况,在获取base的方法作为闭包时,需要先将其转换为变量,即加上var base = base这一行。
考虑如下协议——
protocol ValueHolding {
associatedtype Value
var value: Value { get set }
}
跟随之前的想法,并考虑通过闭包访问属性,可以很容易写出——
struct AnyValueHolder<Value> : ValueHolding {
init<Base : ValueHolding>(_ base: Base) where Base.Value == Value {
var base = base
_value = (
get: { return base.value },
set: { base.value = $0 }
)
}
private let _value: (
get: () -> Value,
set: (Value) -> ()
)
var value: Value {
get { return _value.get() }
set { _value.set(newValue) }
}
}