gpt4 book ai didi

Swift 泛型强制的误解

转载 作者:IT王子 更新时间:2023-10-29 05:36:35 24 4
gpt4 key购买 nike

我正在使用 Signals图书馆。

假设我定义了 BaseProtocol 协议(protocol)和 ChildClass符合 BaseProtocol .

protocol BaseProtocol {}
class ChildClass: BaseProtocol {}

现在我想存储如下信号:
var signals: Array<Signal<BaseProtocol>> = []
let signalOfChild = Signal<ChildClass>()
signals.append(signalOfChild)

我得到错误:

Swift generic error

但是我可以在没有任何编译器错误的情况下编写下一行:
var arrays = Array<Array<BaseProtocol>>()
let arrayOfChild = Array<ChildClass>()
arrays.append(arrayOfChild)

enter image description here

那么,泛型 Swift Array 和泛型 Signal 之间有什么区别呢?

最佳答案

不同的是Array (和 SetDictionary )得到编译器的特殊处理,允许协方差(我更详细地介绍了 in this Q&A )。

然而任意泛型类型是 invariant , 意思是 X<T>是与 X<U> 完全无关的类型如果 T != UT 之间的任何其他类型关系和 U (例如子类型)无关紧要。应用于您的案例,Signal<ChildClass>Signal<BaseProtocol>是不相关的类型,即使 ChildClassBaseProtocol 的子类型(另见 this Q&A )。

这样做的一个原因是它会完全破坏定义与 T 相关的逆变事物(例如函数参数和属性 setter )的泛型引用类型。 .

例如,如果您已实现 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.

这是完全错误的,因为您不能分配 StringInt属性(property)。

为什么这种东西是安全的 Array是它是一种值类型——因此当你这样做时:
let intArray = [2, 3, 4]

var anyArray : [Any] = intArray
anyArray.append("wassup")

没有问题,如 anyArrayintArray 的副本– 因此 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())

一种类型的橡皮擦,可以包裹任何 Signal<T>实例 where 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的异构类型哪里 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 })
}
}

(并重复您要转换为的任何其他协议(protocol)类型)

现在你可以说:
let signals: [AnySignal<BaseProtocol>] = [
AnySignal(childSignal),
AnySignal(anotherSignal)
]

(您实际上可以在此处删除数组的显式类型注释,编译器会推断它是 [AnySignal<BaseProtocol>] – 但如果您要允许更方便的初始化程序,我会保持显式)

要专门创建新实例的值类型或引用类型的解决方案是从 Signal<T> 执行转换。 (其中 T 符合 BaseProtocol )到 Signal<BaseProtocol> .

在 Swift 3.1 中,您可以通过在 Signal 的扩展中定义一个(方便的)初始化程序来实现这一点。类型 where 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,这两种情况下的过程是相似的。 .

关于Swift 泛型强制的误解,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41976844/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com