Swift泛型强制的误解

标签 swift generics

我正在使用 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> 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 的扩展中定义一个(方便的)初始化程序来做到这一点。 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/56757847/

相关文章:

ios - Swift 中的 DateComponents 返回奇怪的值。为什么?

ios - Swift 中的 objective-c 框架中没有这样的模块错误

arrays - 如何使类型化数组符合协议(protocol)

java - 如何在 Scala 中扩展包含泛型方法的 Java 接口(interface)?

java - 将 List<String> 转换为 List<Integer>(或任何扩展 Number 的类)

java - EnumMap 和 Java 泛型

java - 如何将 List<Object> 转换为 List<MyClass>

ios - Swift 3 - 如何提高 Tesseract 的图像质量?

ios - 使用 Codable 使用相同的 key 解码不同的类

swift - Swift 中带有选择器的内联 block /函数