我正在使用信号库.
假设我定义了BaseProtocol协议并且ChildClass符合哪种协议BaseProtocol.
protocol BaseProtocol {}
class ChildClass: BaseProtocol {}
Run Code Online (Sandbox Code Playgroud)
现在我想存储信号,如:
var signals: Array<Signal<BaseProtocol>> = []
let signalOfChild = Signal<ChildClass>()
signals.append(signalOfChild)
Run Code Online (Sandbox Code Playgroud)
我收到错误:
但是我可以编写下一行而没有任何编译器错误:
var arrays = Array<Array<BaseProtocol>>()
let arrayOfChild = Array<ChildClass>()
arrays.append(arrayOfChild)
Run Code Online (Sandbox Code Playgroud)
那么,通用Swift数组和通用信号之间的区别是什么?
不同的是,Array(和Set和Dictionary)从编译器获得特殊待遇,允许协方差(我进入这个稍微更详细的在这个Q&A).
然而,任意泛型类型是不变的,这意味着它X<T>是一个完全不相关的类型,X<U>如果T != U- T和之间的任何其他类型关系U(如子类型)是无关紧要的.应用于您的案例,Signal<ChildClass>并且Signal<BaseProtocol>是不相关的类型,即使ChildClass是子类型BaseProtocol(也参见此问答).
这样做的一个原因是它会完全破坏定义逆变事物(例如函数参数和属性设置器)的通用引用类型T.
例如,如果您已实现Signal为:
class Signal<T> {
var t: T
init(t: T) {
self.t = t
}
}
Run Code Online (Sandbox Code Playgroud)
如果你能够说:
let signalInt = Signal(t: 5)
let signalAny: Signal<Any> = signalInt
Run Code Online (Sandbox Code Playgroud)
你可以说:
signalAny.t = "wassup" // assigning a String to a Signal<Int>'s `t` property.
Run Code Online (Sandbox Code Playgroud)
这是完全错误的,因为你不能分配String给一个Int属性.
这种东西安全的原因Array是它是一种值类型 - 因此当你这样做时:
let intArray = [2, 3, 4]
var anyArray : [Any] = intArray
anyArray.append("wassup")
Run Code Online (Sandbox Code Playgroud)
不存在任何问题,因为anyArray是复制的intArray-这样的逆变append(_:)是没有问题的.
但是,这不能应用于任意通用值类型,因为值类型可以包含任意数量的通用引用类型,这使我们回到了允许对定义逆变事物的泛型引用类型进行非法操作的危险道路.
正如Rob在他的回答中所说,如果你需要维护对同一底层实例的引用,那么引用类型的解决方案就是使用类型擦除器.
如果我们考虑这个例子:
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())
Run Code Online (Sandbox Code Playgroud)
包装符合的任何Signal<T>实例的类型橡皮擦可能如下所示:TBaseProtocol
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)]
Run Code Online (Sandbox Code Playgroud)
现在,我们可以根据异构类型来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) }
}
}
Run Code Online (Sandbox Code Playgroud)
现在我们可以谈论异类型的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.
]
Run Code Online (Sandbox Code Playgroud)
但是,将相同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 })
}
}
Run Code Online (Sandbox Code Playgroud)
(并重复您要转换为的任何其他协议类型)
现在你可以说:
let signals: [AnySignal<BaseProtocol>] = [
AnySignal(childSignal),
AnySignal(anotherSignal)
]
Run Code Online (Sandbox Code Playgroud)
(你实际上可以在这里删除数组的显式类型注释,编译器会推断它是[AnySignal<BaseProtocol>]- 但是如果你要允许更方便的初始化器,我会保持它明确)
您希望专门创建新实例的值类型或引用类型的解决方案是执行从(符合)到的转换.Signal<T>TBaseProtocolSignal<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)
]
Run Code Online (Sandbox Code Playgroud)
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()
]
Run Code Online (Sandbox Code Playgroud)
两种情况下的程序都类似于a struct.
| 归档时间: |
|
| 查看次数: |
772 次 |
| 最近记录: |