不同的是,Array
(和Set
和Dictionary
)從編譯器獲得特殊待遇,允許協方差(我進入這個稍微更詳細in this Q&A)。
然而,任意泛型類型是不變的 - 因此在類型系統的眼睛,Signal<ChildClass>
和Signal<BaseProtocol>
被視爲完全無關的類型,即使ChildClass
可轉換爲BaseProtocol
(見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"
這是完全錯誤,因爲您無法將String
分配給Int
屬性。
之所以這種事情是Array
安全的是,這是一個價值型 - 因此,當你這樣做:
let intArray = [2, 3, 4]
var anyArray : [Any] = intArray
anyArray.append("wassup")
不存在任何問題,因爲anyArray
是複製的intArray
- 從而append(_:)
的逆轉是不成問題的。
但是,這不能應用於任意的泛型值類型,因爲值類型可以包含任意數量的泛型引用類型,這導致我們回退允許非泛型參考類型定義對立變量的非法操作。
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())
A型的橡皮擦,它包裝任何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
函數傳遞給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
方面講,其中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測試版),您可以通過一個擴展,專門定義自己的初始化器爲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)
]
(實際上,你可以刪除顯式類型標註爲陣在這裏,編譯器會推斷出它是[AnySignal<BaseProtocol>]
- 但如果你要允許更多conven ience initialisers,我會保持它明確)
值類型的解決方案,或者要特別創建一個新的實例引用類型,來爲Signal<T>
執行轉換(其中T
符合BaseProtocol
)至Signal<BaseProtocol>
。
在夫特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)
]
預夫特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
。
謝謝,現在我明白了。不幸的是,我不能接受這個評論作爲答案。 – romanilchyshyn
我繼續寫下了答案:) – Hamish