Swift 中的泛型数组

Arrays of Generics in Swift

我一直在研究具有不同类型的泛型 类 数组。用一些示例代码来解释我的问题是最简单的:

// Obviously a very pointless protocol...
protocol MyProtocol {
    var value: Self { get }
}

extension Int   : MyProtocol {  var value: Int    { return self } }
extension Double: MyProtocol {  var value: Double { return self } }

class Container<T: MyProtocol> {
    var values: [T]

    init(_ values: T...) {
        self.values = values
    }

    func myMethod() -> [T] {
        return values
    }
}

现在,如果我尝试像这样创建一个容器数组:

var containers: [Container<MyProtocol>] = []

我收到错误:

Protocol 'MyProtocol' can only be used as a generic constraint because it has Self or associated type requirements.

要解决这个问题,我可以使用 [AnyObject]:

let containers: [AnyObject] = [Container<Int>(1, 2, 3), Container<Double>(1.0, 2.0, 3.0)]
// Explicitly stating the types just for clarity.

但是现在通过containers枚举时又出现了另一个'problem':

for container in containers {
    if let c = container as? Container<Int> {
        println(c.myMethod())

    } else if let c = container as? Container<Double> {
        println(c.myMethod())
    }
}

正如您在上面的代码中看到的,在确定 container 的类型后,在两种情况下都会调用相同的方法。我的问题是:

是否有比转换为每个可能类型的 Container 更好的方法来获得具有正确类型的 Container?或者还有其他我忽略的东西吗?

如果您在 playground 中尝试这个修改后的示例,它会系统性地崩溃:

// Obviously a very pointless protocol...
protocol MyProtocol {
    var value: Int { get }
}

extension Int   : MyProtocol {  var value: Int    { return self } }
//extension Double: MyProtocol {  var value: Double { return self } }

class Container<T: MyProtocol> {
    var values: [T]

    init(_ values: T...) {
        self.values = values
    }
}


var containers: [Container<MyProtocol>] = []

可能他们还在努力,将来可能会发生变化。 不管怎样,到目前为止,我对此的解释是协议不是 具体类型。因此,您现在不知道符合协议的东西在 ram 中占用多少 space(例如,Int 可能不会占用与 Double 相同数量的 ram)。因此,在 ram 中分配数组可能是一个非常棘手的问题。 使用 NSArray 您分配了一个指针数组(指向 NSObjects 的指针)并且它们都占用相同数量的 ram。您可以将 NSArray 视为 具体类型 "pointer to NSObject" 的数组。因此计算内存分配没有问题。

请考虑 Swift 中的 ArrayDictionaryGeneric Struct,而不是 包含指向的指针的对象objects 与 Obj-C 中一样。

希望对您有所帮助。

我将数组声明更改为 AnyObject 的数组,以便可以使用 filter、map 和 reduce(并且还添加了几个要检查的对象)。

let containers: [AnyObject] = [Container<Int>(1, 2, 3), Container<Double>(1.0, 2.0, 3.0), "Hello", "World", 42]

这将允许您在遍历数组之前检查数组中的类型和过滤器

let strings = containers.filter({ return ([=11=] is String) })

println(strings) // [Hello, World]

for ints in containers.filter({ return ([=11=] is Int) }) {
    println("Int is \(foo)") // Int is 42
}

let ints = containers.filter({ return ([=11=] is Container<Int>) })
// as this array is known to only contain Container<Int> instances, downcast and unwrap directly
for i in ints as [Container<Int>] {
    // do stuff
    println(i.values) // [1, 2, 3]
}

这可以用 Equatable 等协议更好地解释。您不能声明数组 [Equatable],因为虽然两个 Int 实例可以相互比较并且 Double 的两个实例可以相互比较,但您不能比较 Int Double 虽然他们都实现了 Equatable.

MyProtocol是一个协议,也就是说它提供了一个通用的接口。不幸的是,您还在定义中使用了 Self 。这意味着每个符合 MyProtocol 的类型将以不同的方式实现它。

你自己写的 - Intvalue 作为 var value: IntMyObjectvalue 作为 var value: MyObject

这意味着一个符合MyProtocol的struct/class不能代替另一个符合MyProtocol的struct/class。这也意味着您不能以这种方式使用 MyProtocol,而不指定具体类型。

如果您将 Self 替换为具体类型,例如AnyObject,会起作用的。但是,目前(Xcode 6.3.1)在编译时会触发分段错误。

这是 "what did you want to happen?" 的一个很好的例子,并且实际演示了如果 Swift 真的有 first-class 类型就会爆炸的复杂性。

protocol MyProtocol {
    var value: Self { get }
}

太棒了。 MyProtocol.value returns 实现它的任何类型,记住这必须在编译时确定,而不是运行时。

var containers: [Container<MyProtocol>] = []

所以,在编译时确定,这是什么类型?忘记编译器,只在纸上做。是的,不确定那是什么类型。我的意思是 具体 类型。没有元类型。

let containers: [AnyObject] = [Container<Int>(1, 2, 3), Container<Double>(1.0, 2.0, 3.0)]

AnyObject 偷偷进入您的签名时,您就知道自己走错了路。这一切都行不通。 AnyObject之后只是麻布。

Or is there something else I've overlooked?

是的。您需要一种类型,但您还没有提供。您提供了约束类型的规则,但没有提供实际类型。回到你真正的问题,并更深入地思考它。 (元类型分析几乎从来不是你的 "real" 问题,除非你正在攻读 CS 博士学位,在这种情况下你会在 Idris 中做这件事,而不是 Swift。)你正在解决什么实际问题?

有一种方法 - 有点 - 做你想做的事 - 有点。有一种方法可以通过协议消除类型限制并仍然获得您想要的结果,有点,但它并不总是很漂亮。这是我针对您的情况提出的协议:

protocol MyProtocol {
    func getValue() -> Self 
}

extension Int: MyProtocol {
    func getValue() -> Int {
        return self
    }
}

extension Double: MyProtocol {
    func getValue() -> Double {
        return self
    }
}

请注意,您最初放入协议声明中的 value 属性 已更改为 returns 对象的方法。

这不是很有趣。

但是现在,因为您已经摆脱了协议中的 value 属性,所以 MyProtocol 可以用作类型,而不仅仅是类型约束。您的 Container class 甚至不再需要通用。您可以这样声明:

class Container {
    var values: [MyProtocol]

    init(_ values: MyProtocol...) {
        self.values = values
    }

    func myMethod() -> [MyProtocol] {
        return values
    }
}

并且由于 Container 不再是通用的,您可以创建 ContainerArray 并遍历它们,打印 myMethod() 方法的结果:

var containers = [Container]()

containers.append(Container(1, 4, 6, 2, 6))
containers.append(Container(1.2, 3.5))

for container in containers {
    println(container.myMethod())
}

//  Output: [1, 4, 6, 2, 6]
//          [1.2, 3.5]

诀窍是构建一个 仅包含通用函数并且不对符合类型提出任何其他要求的协议。 如果您可以这样做,那么您可以使用协议作为一种类型,而不仅仅是一种类型约束。

作为奖励(如果你想这么称呼的话),你的 MyProtocol 值数组甚至可以混合符合 MyProtocol 的不同类型。因此,如果您给 String 一个 MyProtocol 扩展名,如下所示:

extension String: MyProtocol {
    func getValue() -> String {
        return self
    }
}

您实际上可以用混合类型初始化 Container

let container = Container(1, 4.2, "no kidding, this works")

[警告 - 我正在在线游乐场之一中对此进行测试。我还没能在 Xcode 中测试它...]

编辑:

如果您仍然希望 Container 是通用的并且只包含一种类型的对象,您可以通过使 符合它自己的协议来实现:

protocol ContainerProtocol {
    func myMethod() -> [MyProtocol]
}

class Container<T: MyProtocol>: ContainerProtocol {
    var values: [T] = []

    init(_ values: T...) {
        self.values = values
    } 

    func myMethod() -> [MyProtocol] {
        return values.map { [=15=] as MyProtocol }
    }
}

现在您仍然可以 拥有一个 [ContainerProtocol] 对象数组并通过调用 myMethod():

遍历它们
let containers: [ContainerProtocol] = [Container(5, 3, 7), Container(1.2, 4,5)]

for container in containers {
    println(container.myMethod())
}

也许这仍然不适合你,但现在 Container 被限制为单一类型,但你仍然可以遍历 ContainterProtocol 个对象的数组。