60

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

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

我得到错误:

协议“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?或者还有什么我忽略的?

4

5 回答 5

57

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

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不再是通用的,你可以创建一个ArrayofContainer并遍历它们,打印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使用混合类型初始化 a :

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对象数组。

于 2015-05-04T20:59:27.023 回答
8

这是“你发生什么?”的一个很好的例子。并且实际上展示了如果 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。)你在解决什么实际问题?

于 2015-05-04T18:37:57.160 回答
3

这可以通过像Equatable. 你不能声明一个数组[Equatable],因为虽然两个Int实例可以相互比较,并且两个实例Double可以相互比较,但你不能将 anInt与 a进行比较,Double尽管它们都实现了Equatable.

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

你自己写的 - Intwill have valueas var value: Intwhile a MyObjectwill have valueas var value: MyObject

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

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

于 2015-05-04T18:35:16.883 回答
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>] = []

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

考虑到Swift 和Generic StructArray一样,而不是像 Obj-C 中那样包含指向对象的指针的对象。Dictionary

希望这可以帮助。

于 2015-04-22T13:33:42.797 回答
-3

我将数组声明更改为 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]
}
于 2015-04-22T14:11:51.797 回答