Swift通用强制误解
我正在使用信号库。
假设我定义了符合BaseProtocol BaseProtocol协议和ChildClass 。 
 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数组和genericsSignal有什么区别呢?
 不同之处在于Array (和Set和Dictionary )得到了编译器的特殊处理,允许协变(我在这个Q&A中稍微详细地介绍了这一点 )。 
 然而,任意genericstypes是不变的 – 因此在types系统的眼中,即使ChildClass可以转换为BaseProtocol (请参阅本Q&A ), Signal<ChildClass>和Signal<BaseProtocol>也被视为完全不相关的types。 
 其中一个原因是它会完全破坏generics引用types,它定义了关于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" 
 这是完全错误的,因为您不能将一个String分配给一个Int属性。 
 这种事情对Array来说是安全的原因是它是一个值types – 因此当你这样做的时候: 
 let intArray = [2, 3, 4] var anyArray : [Any] = intArray anyArray.append("wassup") 
 没有任何问题,因为anyArray是anyArray的副本 – 因此append(_:)的intArray不成问题。 
然而,这不能应用于任意的通用值types,因为值types可以包含任意数量的通用引用types,这导致我们回避允许对定义逆变事物的通用引用types进行非法操作的危险之路。
正如Rob在他的回答中所说的 ,如果您需要维护对相同底层实例的引用,则参考types的解决scheme是使用types擦除器。
如果我们考虑这个例子:
 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()) 
 包装任何T符合BaseProtocol Signal<T>实例的types擦除器可能如下所示: 
 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的不同types,其中T是符合BaseProtocol某种types。 
 然而,这个包装的一个问题是,我们被限制在BaseProtocol方面的BaseProtocol 。 如果我们有AnotherProtocol并且想要一个符合AnotherProtocol Signal实例的types擦除器呢? 
 解决这个问题的方法之一就是将transform函数传递给type-eraser,使我们能够执行任意的upcast。 
 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的不同types,其中T是可以转化为某个U某种types,这是在创buildtypes橡皮擦时指定的。 
 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的初始化程序来BaseProtocol调用者的负担: 
 extension AnySignal where T == BaseProtocol { init<U : BaseProtocol>(_ base: Signal<U>) { self.init(base, transform: { $0 }) } } 
(并重复您想要转换的任何其他协议types)
现在你可以说:
 let signals: [AnySignal<BaseProtocol>] = [ AnySignal(childSignal), AnySignal(anotherSignal) ] 
  (实际上你可以在这里删除数组的显式types注释,编译器会推断它是[AnySignal<BaseProtocol>] ,但是如果你想允许更多的方便初始化,我会保持它的显式) 
 值types或要创build新实例的引用types的解决scheme是执行从Signal<T> (其中T符合BaseProtocol )到Signal<BaseProtocol> 。 
 在Swift 3.1中,你可以通过在Signaltypes的扩展中定义一个(方便的)初始化程序来实现这一点,其中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来说都是相似的。