【问题标题】:Codable Conformance with Erased Types?与擦除类型的可编码一致性?
【发布时间】:2019-03-02 21:12:42
【问题描述】:

我正在尝试编写一个通用函数来解析几种不同的数据类型。

原来这个方法只适用于 Codable 类型,所以它的泛型类型被<T: Codable> 约束,一切都很好。但是现在,如果返回类型是 Codable,我正在尝试将其扩展为 check,并根据该检查相应地解析数据

func parse<T>(from data: Data) throws -> T? {

    switch T.self {
    case is Codable:

        // convince the compiler that T is Codable

        return try? JSONDecoder().decode(T.self, from: data)

    case is [String: Any].Type:

        return try JSONSerialization.jsonObject(with: data, options: []) as? T

    default:
        return nil
    }

}

所以你可以看到类型检查工作正常,但我坚持让JSONDecoder().decode(:) 接受T 作为Codable 类型,一旦我检查它是。上面的代码没有编译,有错误

Cannot convert value of type 'T' (generic parameter of instance method 'parse(from:)') to expected argument type 'T' (generic parameter of instance method 'decode(_:from:)')In argument type 'T.Type', 'T' does not conform to expected type 'Decodable'

我尝试了许多强制转换技术,例如let decodableT: &lt;T &amp; Decodable&gt; = T.self 等,但都失败了——通常基于Decodable 是协议而T 是具体类型这一事实。

是否可以(有条件地)将协议一致性重新引入到像这样的已擦除类型?我很感激你有任何想法,无论是解决这种方法还是类似的通用解析方法,在这里可能会更成功。

编辑:一个并发症

@vadian 很有帮助地建议创建两个具有不同类型约束的 parse(:) 方法,以使用一个签名来处理所有情况。在许多情况下,这是一个很好的解决方案,如果您以后遇到这个问题,它可能会很好地解决您的难题。

不幸的是,这仅在调用 parse(:) 时已知类型才有效——在我的应用程序中,此方法由另一个泛型方法调用,这意味着该类型已被删除并且编译器可以' t 选择一个正确约束的 parse(:) 实现。

所以,为了澄清这个问题的症结所在:是否可以有条件地/可选地将类型信息(例如协议一致性)back 添加到已擦除的类型中?换句话说,一旦一个类型变成了&lt;T&gt;,有没有办法将它转换为&lt;T: Decodable&gt;

【问题讨论】:

    标签: swift generics casting codable type-erasure


    【解决方案1】:

    您可以有条件地将T.self 转换为Decodable.Type 以获得描述底层Decodable 符合类型的元类型:

      switch T.self {
      case let decodableType as Decodable.Type:
    

    但是,如果我们尝试将decodableType 传递给JSONDecoder,就会出现问题:

    // error: Cannot invoke 'decode' with an argument list of type
    // '(Decodable.Type, from: Data)'
    return try? JSONDecoder().decode(decodableType, from: data)
    

    这不起作用,因为decode(_:from:) 有一个通用占位符T : Decodable

    func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T 
    

    我们不能用Decodable.Type 满足T.Type,因为Decodable doesn't conform to itself

    正如上面链接的问答中所探讨的,解决此问题的一种解决方法是打开 Decodable.Type 值,以便挖掘出符合 Decodable 的底层具体类型——我们可以然后使用满足T

    这可以通过协议扩展来完成:

    extension Decodable {
      static func openedJSONDecode(
        using decoder: JSONDecoder, from data: Data
      ) throws -> Self {
        return try decoder.decode(self, from: data)
      }
    }
    

    我们可以称之为:

    return try? (decodableType.openedJSONDecode(using: JSONDecoder(), from: data) as! T)
    

    您会注意到,我们必须向T 插入强制转换。这是因为通过将T.self 转换为Decodable.Type,我们消除了元类型也描述了T 类型这一事实。因此,我们需要强制转换以获取此信息。

    总而言之,你会希望你的函数看起来像这样:

    func jsonDecode<T>(_ metatype: T.Type, from data: Data) throws -> T {
      switch metatype {
      case let codableType as Decodable.Type:
        let decoded = try codableType.openedJSONDecode(
          using: JSONDecoder(), from: data
        )
    
        // The force cast `as! T` is guaranteed to always succeed due to the fact
        // that we're decoding using `metatype: T.Type`. The cast to
        // `Decodable.Type` unfortunately erases this information.
        return decoded as! T
    
      case is [String: Any].Type, is [Any].Type:
        let rawDecoded = try JSONSerialization.jsonObject(with: data, options: [])
        guard let decoded = rawDecoded as? T else {
          throw DecodingError.typeMismatch(
            type(of: rawDecoded), .init(codingPath: [], debugDescription: """
            Expected to decode \(metatype), found \(type(of: rawDecoded)) instead.
            """)
          )
        }
        return decoded
    
      default:
        fatalError("\(metatype) is not Decodable nor [String: Any] nor [Any]")
      }
    }
    

    我还做了一些其他更改:

    • 将返回类型从 T? 更改为 T。通常,您要么希望通过让函数返回一个可选值或让它抛出来处理错误——调用者同时处理这两种方式可能会很混乱。

    • T.Type 添加了显式参数。这避免了依赖调用者使用返回类型推断来满足T,IMO 与返回类型which is discouraged by the API design guidelines 的重载类似。

    • default: 设为fatalError,因为提供不可解码类型可能是程序员错误。

    【讨论】:

    • 这是一个非常强大且通用的答案,但对 openedJSONDecode 的需求感觉就像是指向 Swift 泛型问题的语法技巧。信息在那里,但编译器在不翻转语法的情况下无法弄清楚。这比as! T 更让我烦恼(这绝对让我烦恼,因为它觉得编译器应该能够证明这一点)。我只是在这里喃喃自语,但如果有任何更深入的讨论,我很感兴趣。
    • @RobNapier 是的,我们肯定正在解决当前语言的限制!自己打开存在的能力is a part of the generics manifesto,所以如果/当实现它时,将不再需要(ab)使用这样的协议扩展。
    • 另一个被抛出的想法是编译器可以在将存在作为参数传递给泛型类型参数时为您隐式打开存在,因此您可以将Decodable.Type 直接传递给@ 987654357@ 参数,其中T : Decodable 和编译器会自动为您解开Decodable.Type
    • 但即使存在存在的开放,您仍然需要在末尾使用as! T 才能回到具体类型(而不是Decodable)。语言真正需要的是某种在运行时有条件地约束通用占位符的方法,例如if T : Decodable { /* within this scope, T is now Decodable */ }。不过,我不记得之前关于此类功能的任何讨论。
    • 感谢您对@Hamish 的如此深入的回复——这非常有效!
    猜你喜欢
    • 2018-07-24
    • 1970-01-01
    • 2021-01-19
    • 1970-01-01
    • 1970-01-01
    • 2020-08-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多