不同之处在于Array
(and Set
and Dictionary
)从编译器获得特殊待遇,允许协变(我对此进行了更详细的介绍在本次问答中 https://stackoverflow.com/q/37188580/2976878).
然而任意泛型类型是不变的 https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science), 意思是X<T>
是一个完全不相关的类型X<U>
if T != U
– 之间的任何其他类型关系T
and U
(例如子类型)是无关紧要的。应用到你的案例中,Signal<ChildClass>
and Signal<BaseProtocol>
是不相关的类型,即使ChildClass
是一个子类型BaseProtocol
(也可以看看本次问答 https://stackoverflow.com/q/38590548/2976878).
原因之一是它会完全破坏定义逆变事物(例如函数参数和属性设置器)的通用引用类型T
.
例如,如果您已经实施了Signal
as:
class Signal<T> {
var t: T
init(t: T) {
self.t = t
}
}
If你可以说:
let signalInt = Signal(t: 5)
let signalAny: Signal<Any> = signalInt
然后你可以说:
signalAny.t = "wassup" // assigning a String to a Signal<Int>'s `t` property.
这是完全地错误的,因为你不能分配String
to an Int
财产。
这种东西之所以安全Array
是它是一个值类型 - 因此当你这样做时:
let intArray = [2, 3, 4]
var anyArray : [Any] = intArray
anyArray.append("wassup")
没有问题,因为anyArray
is a copy of intArray
– 因此逆变append(_:)
不是问题。
但是,这不能应用于任意泛型值类型,因为值类型可以包含任意数量的泛型引用类型,这使我们回到了允许对定义逆变事物的泛型引用类型进行非法操作的危险道路。
正如罗布所说 https://stackoverflow.com/a/38591285/2976878在他的回答中,如果您需要维护对同一底层实例的引用,则引用类型的解决方案是使用类型擦除器。
如果我们考虑这个例子:
protocol BaseProtocol {}
class ChildClass: BaseProtocol {}
class AnotherChild : BaseProtocol {}
class Signal<T> {
var t: T
init(t: T) {
self.t = t
}
}
let childSignal = Signal(t: ChildClass())
let anotherSignal = Signal(t: AnotherChild())
一个橡皮擦,可以包裹任何Signal<T>
实例,其中T
符合BaseProtocol
可能看起来像这样:
struct AnyBaseProtocolSignal {
private let _t: () -> BaseProtocol
var t: BaseProtocol { return _t() }
init<T : BaseProtocol>(_ base: Signal<T>) {
_t = { base.t }
}
}
// ...
let signals = [AnyBaseProtocolSignal(childSignal), AnyBaseProtocolSignal(anotherSignal)]
现在让我们讨论异质类型Signal
哪里的T
是某种符合BaseProtocol
.
然而,这个包装器的一个问题是我们仅限于谈论BaseProtocol
。如果我们有怎么办AnotherProtocol
想要一个橡皮擦Signal
实例,其中T
符合AnotherProtocol
?
解决这个问题的一种方法是通过transform
函数到类型擦除器,允许我们执行任意向上转换。
struct AnySignal<T> {
private let _t: () -> T
var t: T { return _t() }
init<U>(_ base: Signal<U>, transform: @escaping (U) -> T) {
_t = { transform(base.t) }
}
}
现在我们可以用异质类型来讨论Signal
where T
是某种可以转换为某种类型的类型U
,在创建类型擦除器时指定。
let signals: [AnySignal<BaseProtocol>] = [
AnySignal(childSignal, transform: { $0 }),
AnySignal(anotherSignal, transform: { $0 })
// or AnySignal(childSignal, transform: { $0 as BaseProtocol })
// to be explicit.
]
然而,同样的经过transform
每个初始化器的函数有点笨拙。
在 Swift 3.1(随 Xcode 8.3 beta 提供)中,您可以通过专门为以下对象定义自己的初始化程序来减轻调用者的负担:BaseProtocol
在扩展中:
extension AnySignal where T == BaseProtocol {
init<U : BaseProtocol>(_ base: Signal<U>) {
self.init(base, transform: { $0 })
}
}
(并对您想要转换成的任何其他协议类型重复此操作)
现在你可以说:
let signals: [AnySignal<BaseProtocol>] = [
AnySignal(childSignal),
AnySignal(anotherSignal)
]
(You can actually remove the explicit type annotation for the array here, and the compiler will infer it to be [AnySignal<BaseProtocol>]
– but if you're going to allow for more convenience initialisers, I would keep it explicit)
针对值类型或您想要专门处理的引用类型的解决方案create一个新实例,要执行转换 from Signal<T>
(where T
符合BaseProtocol
) to Signal<BaseProtocol>
.
在 Swift 3.1 中,您可以通过在扩展中定义一个(方便的)初始化器来完成此操作Signal
类型其中T == BaseProtocol
:
extension Signal where T == BaseProtocol {
convenience init<T : BaseProtocol>(other: Signal<T>) {
self.init(t: other.t)
}
}
// ...
let signals: [Signal<BaseProtocol>] = [
Signal(other: childSignal),
Signal(other: anotherSignal)
]
在 Swift 3.1 之前,这可以通过实例方法来实现:
extension Signal where T : BaseProtocol {
func asBaseProtocol() -> Signal<BaseProtocol> {
return Signal<BaseProtocol>(t: t)
}
}
// ...
let signals: [Signal<BaseProtocol>] = [
childSignal.asBaseProtocol(),
anotherSignal.asBaseProtocol()
]
两种情况下的程序都是相似的struct
.