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 中的 Array
和 Dictionary
是 Generic 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
的类型将以不同的方式实现它。
你自己写的 - Int
将 value
作为 var value: Int
而 MyObject
将 value
作为 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
不再是通用的,您可以创建 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]
诀窍是构建一个 仅包含通用函数并且不对符合类型提出任何其他要求的协议。 如果您可以这样做,那么您可以使用协议作为一种类型,而不仅仅是一种类型约束。
作为奖励(如果你想这么称呼的话),你的 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
个对象的数组。
我一直在研究具有不同类型的泛型 类 数组。用一些示例代码来解释我的问题是最简单的:
// 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 中的 Array
和 Dictionary
是 Generic 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
的类型将以不同的方式实现它。
你自己写的 - Int
将 value
作为 var value: Int
而 MyObject
将 value
作为 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
不再是通用的,您可以创建 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]
诀窍是构建一个 仅包含通用函数并且不对符合类型提出任何其他要求的协议。 如果您可以这样做,那么您可以使用协议作为一种类型,而不仅仅是一种类型约束。
作为奖励(如果你想这么称呼的话),你的 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
个对象的数组。