首页 文章

Swift中的泛型数组

提问于
浏览
45

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

// 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 进行枚举时会出现另一个'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 的类型之后,在两种情况下都会调用相同的方法 . 我的问题是:

Is there a better way to get the Container with the correct type than casting to every possible type of Container? Or is there something else I've overlooked?

5 回答

  • -3

    有一种方式 - 有点 - 做你想做的事 - 有点儿 . 有一种方法,有协议,消除类型限制,仍然得到你想要的结果,但它并不总是漂亮 . 以下是我在您的情况下作为协议提出的内容:

    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 不再是通用的,你可以创建一个 ArrayContainer 并迭代它们,打印 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 对象的数组 .

  • 46

    这是“你想要发生什么?”的一个很好的例子 . 并且实际上展示了如果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 只是麻布 .

    还是有其他我忽略的东西?

    是 . 你需要一个类型,但你没有提供 . 您提供了约束类型的规则,但没有实际类型 . 回到你真正的问题,并更深入地思考它 . (元数据分析几乎不是你的“真正的”问题,除非你正在攻读CS博士,在这种情况下你会在伊德里斯做这个,而不是斯威夫特 . )你解决了什么实际问题?

  • 1

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

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

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

    这意味着不能使用符合 MyProtocol 的结构/类符合 MyProtocol 的另一个结构/类的位置 . 这也意味着您不能以这种方式使用 MyProtocol ,而无需指定具体类型 .

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

  • 3

    如果您在游乐场中尝试此修改示例,它将系统崩溃:

    // 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中有多少符合协议的东西(例如 Int 可能不会占用与 Double 相同数量的ram) . 因此,在ram中分配数组可能是一个非常棘手的问题 . 使用 NSArray 你分配一个指针数组(指向 NSObjects 的指针),它们都占用相同数量的ram . 您可以将 NSArray 视为具体类型“指向 NSObject 的指针”的数组 . 因此计算ram分配没有问题 .

    考虑到Swift中的 Array 以及 Dictionary 是Generic Struct,而不是像Obj-C中那样包含指向对象的指针的对象 .

    希望这可以帮助 .

  • 7

    我将数组声明更改为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 ($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]
    }
    

相关问题