我正在使用Signals库。
假设我定义了BaseProtocol协议,ChildClass
符合BaseProtocol
。
protocol BaseProtocol {}
class ChildClass: BaseProtocol {}
现在我想存储以下信号:
var signals: Array<Signal<BaseProtocol>> = []
let signalOfChild = Signal<ChildClass>()
signals.append(signalOfChild)
我收到错误:
但是我可以编写下一行而没有任何编译器错误:
var arrays = Array<Array<BaseProtocol>>()
let arrayOfChild = Array<ChildClass>()
arrays.append(arrayOfChild)
那么,通用Swift数组和通用信号之间的区别是什么?
答案 0 :(得分:9)
区别在于Array
(以及Set
和Dictionary
)得到了编译器的特殊处理,允许协方差(我稍微详细一点in this Q&A )。
然而,任意泛型类型为invariant,这意味着如果X<T>
X<U>
与T != U
之间的任何其他类型关系,则T
与U
完全无关。 Signal<ChildClass>
(例如子类型)无关紧要。适用于您的案例,Signal<BaseProtocol>
和ChildClass
是不相关的类型,即使BaseProtocol
是[{1}}的子类型(另请参阅this Q&A)。
这样做的一个原因是它会完全破坏定义与T
相关的逆变事物(例如函数参数和属性设置器)的泛型引用类型。
例如,如果您已将Signal
实施为:
class Signal<T> {
var t: T
init(t: T) {
self.t = t
}
}
如果你可以说:
let signalInt = Signal(t: 5)
let signalAny: Signal<Any> = signalInt
然后你可以说:
signalAny.t = "wassup" // assigning a String to a Signal<Int>'s `t` property.
完全错误,因为您无法将String
分配给Int
属性。
这种事情对Array
是安全的原因是它是一种值类型 - 因此当你这样做时:
let intArray = [2, 3, 4]
var anyArray : [Any] = intArray
anyArray.append("wassup")
没有问题,因为anyArray
是intArray
的副本 - 因此append(_:)
的逆转不是问题。
但是,这不能应用于任意通用值类型,因为值类型可以包含任意数量的通用引用类型,这使我们回到了允许对定义逆变事物的泛型引用类型进行非法操作的危险道路。 / p>
As Rob says在他的回答中,参考类型的解决方案,如果你需要维护对同一底层实例的引用,就是使用类型擦除器。
如果我们考虑这个例子:
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
的{{1}}个实例,我们有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
现在我们可以讨论T
的异类型,其中U
是某种可转换为某种let signals: [AnySignal<BaseProtocol>] = [
AnySignal(childSignal, transform: { $0 }),
AnySignal(anotherSignal, transform: { $0 })
// or AnySignal(childSignal, transform: { $0 as BaseProtocol })
// to be explicit.
]
的类型,在创建类型时指定-eraser。
transform
然而,将相同的BaseProtocol
函数传递给每个初始化函数有点笨拙。
在Swift 3.1(Xcode 8.3 beta版)中,您可以通过在扩展程序中专门为extension AnySignal where T == BaseProtocol {
init<U : BaseProtocol>(_ base: Signal<U>) {
self.init(base, transform: { $0 })
}
}
定义自己的初始化程序来解除调用者的负担:
let signals: [AnySignal<BaseProtocol>] = [
AnySignal(childSignal),
AnySignal(anotherSignal)
]
(并重复您要转换为的任何其他协议类型)
现在你可以说:
[AnySignal<BaseProtocol>]
(您实际上可以在此处删除数组的显式类型注释,编译器会将其推断为Signal<T>
- 但如果您要允许更方便的初始化器,我会保持明确的)
您希望专门创建新实例的值类型或引用类型的解决方案是从T
执行转换(其中{ {1}}符合BaseProtocol
)至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)
]
Pre 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
相似。