Swift 中的泛型数组

Posted

技术标签:

【中文标题】Swift 中的泛型数组【英文标题】:Arrays of Generics in Swift 【发布时间】:2015-04-22 11:50:24 【问题描述】:

我一直在玩不同类型的泛型类数组。用一些示例代码最容易解释我的问题:

// 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' 只能用作通用约束,因为它具有 Self 或关联的类型要求。

要解决这个问题,我可以使用[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枚举时出现了另一个“问题”:

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 的所有可能类型?或者还有什么我忽略的?

【问题讨论】:

这里不适合使用“链接列表”吗?类 ContainerNode var head: ContainerNode var next: ContainerNode? 【参考方案1】:

有一种方法——有点——做你想做的——有点。有一种使用协议的方法可以消除类型限制,并且仍然可以获得您想要的结果,但它并不总是很漂亮。这是我在您的情况下提出的协议:

protocol MyProtocol 
    func getValue() -> Self 


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


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

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

这不是很有趣。

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

class Container 
    var values: [MyProtocol]

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

    func myMethod() -> [MyProtocol] 
        return values
    

由于Container 不再是通用的,您可以创建Containers 的Array 并遍历它们,打印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  $0 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 对象数组。

【讨论】:

在 OP 中,从来不需要使容器通用。 如果 OP 希望 Container 有一个 MyProtocol 对象数组,只要 MyProtocol 具有必需的 value 属性,Container 就必须是通用的。 感谢您的回答。不幸的是,我要求 Container 只存储一种类型,但这是一个有趣的解决方案,我相信以后我会找到它的一些用途。 我最终接受了@Rob Napier 的建议,并提出了不同的解决方案。但是我觉得这为原始问题提供了一个很好的答案。再次感谢! 这是一个非常有趣的解决方案。当您深入了解values 的内容时,它会变得更加有趣。它只是[MyProtocol],所以你唯一可以调用它的是getValue(),它会给你像多态一样的动态调度,但不会让你偷看。 getValue() 的结果只是另一个 MyProtocol,而不是 IntDouble。所以你不能应用+ 或任何其他不在协议中的“类似数字”的东西,只能再次应用getValue()。基本上Self 会蒸发而getValue() 没有做任何有用的事情。但仍然是非常有趣的探索。【参考方案2】:

这是“您想要发生什么?”的一个很好的例子。并且实际上展示了如果 Swift 拥有真正一流的类型,那么复杂性就会爆炸式增长。

protocol MyProtocol 
    var value: Self  get 

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

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

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

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

AnyObject 潜入您的签名时,您就知道自己走错了路。这一切都不会奏效。 AnyObject 之后只是麻布。

或者还有什么我忽略的?

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

【讨论】:

感谢您的洞察力。 “你在解决什么实际问题?”让我重新思考我真正想要实现的目标。使用不同的方法尝试将所有内容存储在数组中并进行一些重构,一切都可以在不使用 AnyObject 的情况下运行。 Swift 泛型仍然是原始的。在其他语言中很简单的东西在 Swift 中仍然是不可能的或过于庞大。无需让 OP 回到真正的问题。 嘿,Rob,您的观察对我也有很大帮助。我来到了一个“太通用”的位置,而不是使用类型,我只是使用“任何”作为解决方法。我还在想我是不是走错了路,否则 Swift 仍然不够“成熟”,无法做到这一点。无论如何,谢谢!【参考方案3】:

这可以用Equatable 之类的协议更好地解释。您不能声明数组[Equatable],因为虽然两个Int 实例可以相互比较,并且Double 的两个实例可以相互比较,但您不能将IntDouble 进行比较,尽管它们都是实现Equatable

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

你自己写的 - Int 将有 value 作为 var value: IntMyObject 将有 value 作为 var value: MyObject

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

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

【讨论】:

>你不能将 Int 与 Double 进行比较——你可以,但 Swift 缺乏自动转换。【参考方案4】:

如果你在操场上尝试这个修改过的例子,它会系统性地崩溃:

// 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>] = []

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

考虑到 Swift 中的 ArrayDictionary通用结构,而不是像 Obj-C 中的包含指向对象的指针的对象

希望这会有所帮助。

【讨论】:

【参考方案5】:

我将数组声明更改为 AnyObject 数组,以便可以使用过滤器、映射和归约(还添加了更多对象以进行检查)。

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

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

let strings = containers.filter( return ($0 is String) )

println(strings) // [Hello, World]

for ints in containers.filter( return ($0 is Int) ) 
    println("Int is \(foo)") // Int is 42


let ints = containers.filter( return ($0 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]

【讨论】:

以上是关于Swift 中的泛型数组的主要内容,如果未能解决你的问题,请参考以下文章

数组中的泛型联合

Java中的泛型数组

Java数组中的泛型

OC的泛型使用介绍

如何从 C# 中的泛型类型数组中选择一组随机值?

Java中的泛型