Swift中的generics数组
我一直在玩与不同types的generics类的数组。 用一些示例代码来解释我的问题是最简单的:
// 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 } }
现在,如果我尝试创build一个像这样的容器数组:
var containers: [Container<MyProtocol>] = []
我得到的错误:
协议“MyProtocol”只能用作通用约束,因为它具有自我或相关types的要求。
为了解决这个问题,我可以使用[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
的types之后,在两种情况下调用相同的方法。 我的问题是:
有没有更好的方式来获得正确types的Container
,而不是投射到每种可能types的Container
? 还是有什么我忽略了?
有一种方法可以做你想做的事情。 有一种方式,通过协议,消除types限制,仍然得到你想要的结果,种类,但它并不总是漂亮。 下面是我在你的情况下提出的协议:
protocol MyProtocol { func getValue() -> Self } extension Int: MyProtocol { func getValue() -> Int { return self } } extension Double: MyProtocol { func getValue() -> Double { return self } }
请注意,最初放在协议声明中的value
属性已更改为返回对象的方法。
这不是很有趣。
但现在,因为你已经摆脱了协议中的value
属性, MyProtocol
可以被用作一个types,而不仅仅是一个types约束。 你的Container
类甚至不需要通用了。 你可以这样声明:
class Container { var values: [MyProtocol] init(_ values: MyProtocol...) { self.values = values } func myMethod() -> [MyProtocol] { return values } }
由于Container
不再是通用的,因此可以创build一个Container
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]
诀窍是构build一个只包含generics函数的协议, 并且对合规types没有其他要求。 如果你能这样做,那么你可以使用协议作为types,而不仅仅是一个types约束。
作为奖励(如果你想这样做的话),你的MyProtocol
值数组甚至可以混合符合MyProtocol
不同types。 所以如果你给String
一个MyProtocol
扩展,像这样:
extension String: MyProtocol { func getValue() -> String { return self } }
实际上你可以使用混合types初始化一个Container
:
let container = Container(1, 4.2, "no kidding, this works")
[警告 – 我正在其中一个在线游乐场进行testing。 我还没有能够在Xcode中testing它…]
编辑:
如果你仍然希望Container
是通用的,只保存一种types的对象,你可以通过使其符合自己的协议来实现:
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
被限制为一种types,但仍然可以遍历一个ContainterProtocol
对象的数组。
这是“你想要发生什么?”的一个很好的例子。 而实际上,如果Swift真的具有一stream的types,实际上就展现了爆炸的复杂性。
protocol MyProtocol { var value: Self { get } }
大。 MyProtocol.value
返回任何types的实现它,记住这必须在编译时确定,而不是运行时。
var containers: [Container<MyProtocol>] = []
那么,在编译时确定,这是什么types? 忘记编译器,只是在纸上做。 是的,不知道是什么types的。 我的意思是具体types。 没有元types。
let containers: [AnyObject] = [Container<Int>(1, 2, 3), Container<Double>(1.0, 2.0, 3.0)]
当AnyObject
偷偷签名时,你知道你走错了路。 没有什么关于这是永远不会工作。 AnyObject
之后只有麻布。
还是有什么我忽略了?
是。 你需要一个types,你没有提供一个。 你已经提供了一个限制types的规则,但是没有实际的types。 回到你真正的问题,并深入思考。 (Metatype分析几乎不是你的“真正的”问题,除非你在CS博士工作,在这种情况下,你会在Idris,而不是Swift做这个)。你究竟在解决什么问题?
这可以用像Equatable
这样的协议来解释。 你不能声明一个数组[Equatable]
因为虽然两个Int
实例可以相互比较,并且Double
两个实例可以相互比较,但不能将Int
与Double
进行比较,尽pipe它们都实现了Equatable
。
MyProtocol
是一个协议,这意味着它提供了一个通用的接口。 不幸的是,你也在定义中使用了Self
。 这意味着每个符合MyProtocol
types都会以MyProtocol
方式实现。
你已经自己写了 – Int
将有value
作为var value: Int
而MyObject
将value
作为var value: MyObject
。
这意味着符合MyProtocol
的结构/类不能用于符合MyProtocol
的另一个结构/类。 这也意味着你不能以这种方式使用MyProtocol
,而不指定具体的types。
如果你用一个具体的types来replace这个Self
,比如AnyObject
,它就可以工作。 但是,目前(Xcode 6.3.1)编译时会触发分段错误)。
如果您在操场上尝试这个修改过的示例,系统将会崩溃:
// 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>] = []
可能他们仍然在为此工作,事情可能会在未来发生变化。 无论如何,我现在的解释是协议不是一个具体的types 。 因此,现在不需要多less空间符合协议需要的内存(例如Int
可能不会占用与Double
相同的内存)。 因此,在RAM中分配数组可能是一个相当棘手的问题。 使用一个NSArray
你分配一个指针数组(指向NSObjects
指针),它们都占用相同数量的内存。 你可以把NSArray
想象成一个具体types “指向NSObject
”的数组。 因此计算内存分配没有问题。
考虑到Array
和Swift中的Dictionary
都是Generic Struct ,而不是Obj-C中包含指向对象的指针的对象 。
希望这可以帮助。
我将数组声明更改为AnyObject数组,以便可以使用filter,映射和缩减(还可以添加更多的对象来检查)。
let containers: [AnyObject] = [Container<Int>(1, 2, 3), Container<Double>(1.0, 2.0, 3.0), "Hello", "World", 42]
这将允许您检查数组中的types并在循环数组之前进行筛选
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] }