【问题标题】:Using a Type Variable in a Generic在泛型中使用类型变量
【发布时间】:2015-06-18 01:25:19
【问题描述】:

我有this question,除了 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>()
    }
}

【问题讨论】:

    标签: swift generics


    【解决方案1】:

    Swift 的static typing 表示必须在编译时知道变量的类型。

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

    在使用协议时,Swift 使用动态调度,所以你可以写Array&lt;MyProtocol&gt;(),而数组只是存储对实现MyProtocol 的东西的引用——所以当你从数组中取出东西时,您可以访问MyProtocol 所需的所有函数/变量/类型别名。

    但如果t 实际上是一个类似于Any.Type 的变量,那么Array&lt;t&gt;() 就毫无意义,因为它的类型实际上在编译时是未知的。 (由于Array 是泛型结构,编译器需要知道使用哪种类型作为泛型参数,但这是不可能的。)

    我建议今年观看一些来自 WWDC 的视频:

    我发现这张幻灯片对理解协议和动态调度特别有帮助:

    【讨论】:

    • 嗯,这就是问题的核心所在。我想我应该看看那些视频!
    【解决方案2】:

    有一种方法叫做泛型。你可以这样做。

    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>()
    

    【讨论】:

    • 这将修复我的示例,但它通常不起作用。如果你想要传入一个可变大小的类型数组怎么办?我实际上有,但我想发布一个简单的例子。我将添加一个泛型不起作用的示例。
    • 不过仍然是一个很好的答案。这在我在编译时足够了解的情况下很有帮助。但我通过网络连接接收类型并尝试从它们构建对象,所以我正在寻找完全通用的解决方案。
    【解决方案3】:

    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)
    

    【讨论】:

      【解决方案4】:

      我会用你从第一个答案中学到的东西来分解它。我冒昧地重构了一些代码。这里是:

      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&lt;protocol&lt;&gt;.Type&gt; 类型,不能转换为任何东西。
      • 奇怪的是,每当我尝试在其中放入任何动态而不是 Int.self 时,都会失败。像这样:var type:Any.Type ; if (rand()%2 == 0){ type = Int.self } ; else { type = String.self } ; let ts = someTypes(type);
      • 我不喜欢为此收到的错误消息。就像 Swift 正在制定规则一样。
      【解决方案5】:

      只要您可以向编译器提供有关...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&lt;String&gt;(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>
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-04-21
        • 2011-09-28
        • 1970-01-01
        • 1970-01-01
        • 2021-09-21
        • 2011-05-01
        • 1970-01-01
        相关资源
        最近更新 更多