在通用类型中使用类型变量

26

除了Swift,我已经解决了这个问题。如何在泛型中使用Type变量?

我尝试过这样做:

func intType() -> Int.Type {
    return Int.self
}

func test() {
    var t = self.intType()
    var arr = Array<t>() // Error: "'t' is not a type". Uh... yeah, it is.
}

这个也没用:

var arr = Array<t.Type>() // Error: "'t' is not a type"
var arr = Array<t.self>() // Swift doesn't seem to even understand this syntax at all.

有办法做到这一点吗?我感觉Swift不支持它,并且给了我一些模糊的错误信息。
编辑:这里是一个更复杂的例子,无法使用通用函数头绕过问题。当然这没有意义,但我在我的代码中有一个合理的使用这种功能的地方,而不是发布我的实际代码的干净示例。
func someTypes() -> [Any.Type] {
    var ret = [Any.Type]()
    for (var i = 0; i<rand()%10; i++) {
        if (rand()%2 == 0){ ret.append(Int.self) }
        else {ret.append(String.self) }
    }
    return ret
}

func test() {
    var ts = self.someTypes()

    for t in ts {
        var arr = Array<t>()
    }
}
5个回答

36

Swift的静态类型意味着变量的类型必须在编译时已知。

在泛型函数func foo<T>() { ... }的上下文中,T看起来像一个变量,但它的类型实际上是根据调用函数的位置在编译时已知的。 Array<T>()的行为取决于T,但这些信息在编译时是已知的。

使用协议时,Swift采用动态分派,因此您可以编写Array<MyProtocol>(),数组仅存储实现MyProtocol的内容的引用 - 因此,当从数组中获取某些内容时,您可以访问MyProtocol所需的所有函数/变量/类型别名。

但是,如果t实际上是一个Any.Type类型的变量,Array<t>()是没有意义的,因为它的类型在编译时实际上是不知道的。(由于Array是一个泛型结构体,编译器需要知道使用哪个类型作为泛型参数,但这是不可能的。)
我建议观看一些今年WWDC的视频: 我发现这张幻灯片特别有助于理解协议和动态分派:


嗯,这就是问题的核心。我想我应该看那些视频! - sudo

7
有一种方法叫做泛型(generics)。你可以这样实现。
class func foo() {
    test(Int.self)
}

class func test<T>(t: T.Type) {
    var arr = Array<T>()
}

您需要在某种方式下提示编译器所需特化的函数类型。另一种方式是使用返回参数(在该情况下被丢弃)。
class func foo() {
    let _:Int = test()
}

class func test<T>() -> T {
    var arr = Array<T>()
}

在一个类(或结构体)上使用泛型时,您不需要额外的参数:

class Whatever<T> {
    var array = [T]() // another way to init the array.
}

let we = Whatever<Int>()

这可以解决我的例子,但并不适用于一般情况。如果你有一个变量大小的类型数组要传入怎么办?我实际上有这样的情况,但我想发布一个简单的例子。我会添加一个泛型无法解决的例子。 - sudo
虽然这仍然是一个好答案。在编译时我知道足够的信息,这对我很有帮助。但是我正在通过网络连接接收类型,并尝试从它们构建对象,因此我正在寻找完全通用的解决方案。 - sudo

5

jtbandes的回答——你不能使用当前的方法是正确的,因为Swift是静态类型的。

然而,如果你愿意在数组中创建允许的类型白名单,比如在一个enum中,你可以在运行时动态初始化不同的类型。

首先,创建一个允许的类型的enum

enum Types {
    case Int
    case String
}

创建一个Example类。实现你的someTypes()函数来使用这些枚举值。(你可以很容易地将一个JSON字符串数组转换为此枚举数组。)
class Example {
    func someTypes() -> [Types] {
        var ret = [Types]()
        for _ in 1...rand()%10 {
            if (rand()%2 == 0){ ret.append(.Int) }
            else {ret.append(.String) }
        }
        return ret
    }

现在,使用switch语句来为每种允许的类型限定arr,并实现你的测试函数:

    func test() {
        let types = self.someTypes()

        for type in types {
            switch type {
            case .Int:
                var arr = [Int]()
                arr += [4]

            case .String:
                var arr = [String]()
                arr += ["hi"]
            }
        }
    }
}

你可能已经知道,你可以选择将arr声明为[Any]以混合类型(即jtbandes的回答中的“异构”情况):

var arr = [Any]()

for type in types {
    switch type {
    case .Int:
        arr += [4]

    case .String:
        arr += ["hi"]
    }
}

print(arr)

3

我会将这个问题分解成你从第一个答案中学习到的内容。我已经自己重新编写了一些代码。以下是最终代码:

func someTypes<T>(t: T.Type) -> [Any.Type] {
    var ret = [Any.Type]()
    for _ in 0..<rand()%10 {
        if (rand()%2 == 0){ ret.append(T.self) }
        else {
            ret.append(String.self)
        }
    }
    return ret
}


func makeArray<T>(t: T) -> [T] {
    return [T]()
}

func test() {
    let ts = someTypes(Int.self)
    for t in ts {
        print(t)
    }
}

这个方法有些可行,但我认为它的实现方式非常不规范。你能否使用反射(镜像)代替?


这很有趣和创意,但我认为它不可用。试想在test()for循环中添加 var arr = makeArray(t)arr将会是一种 Array<protocol<>.Type> 类型,无法转换成任何类型。 - Aaron Brager
奇怪的是,每当我尝试在那里放置任何动态内容而不是 Int.self 时,它就会失败。像这样:var type:Any.Type ; if (rand()%2 == 0){ type = Int.self } ; else { type = String.self } ; let ts = someTypes(type); - sudo
1
我不喜欢我得到的这个错误消息。就像Swift随心所欲地制定规则一样。 - sudo

0

只要您能向编译器提供关于类型 T 的“提示”,就可以实现这一点。因此,在下面的示例中,必须使用 : String?

func cast<T>(_ value: Any) -> T? {
    return value as? T
}

let inputValue: Any = "this is a test"
let casted: String? = cast(inputValue) 
print(casted) // Optional("this is a test")
print(type(of: casted)) // Optional<String>

为什么 Swift 不允许我们直接使用 let casted = cast<String>(inputValue),我永远不会明白。


一个令人烦恼的情况是当你的函数没有返回值时。这时提供必要的“提示”并不总是那么容易。让我们看看这个例子...
func asyncCast<T>(_ value: Any, completion: (T?) -> Void) {
    completion(value as? T)
}

以下客户端代码无法编译。它会出现“无法推断泛型参数'T'”的错误。
let inputValue: Any = "this is a test"
asyncCast(inputValue) { casted in
    print(casted) 
    print(type(of: casted))
}

但是您可以通过以下方式向编译器提供“提示”来解决这个问题:

asyncCast(inputValue) { (casted: String?) in
    print(casted) // Optional("this is a test")
    print(type(of: casted)) // Optional<String>
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接