带擦除类型的可编码一致性?

时间:2019-03-02 21:12:42

标签: swift generics casting codable type-erasure

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

最初,此方法仅适用于Codable类型,因此其通用类型受<T: 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: <T & Decodable> = T.self等,但都失败了-通常是基于以下事实:Decodable是一个协议,T是一个协议具体类型。

是否可以(有条件地)将协议一致性重新引入这种擦除类型?如果您有任何想法,无论是解决该方法还是在这里可能更成功的通用解析方法,我都将不胜感激。

编辑:并发症

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

不幸的是,这仅在调用parse(:)时知道类型的情况下有效-在我的应用程序中,此方法已由另一个通用方法调用,这意味着该类型已被擦除并且编译器无法选择正确约束的parse(:)实现。

因此,为了澄清这个问题的症结所在:是否可以有条件/可选地将类型信息(例如协议一致性) back 添加到已擦除的类型?换句话说,一旦类型变为<T>,是否有任何方法可以将其强制转换为<T: Decodable>

1 个答案:

答案 0 :(得分:2)

您可以有条件地将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 

我们不能用T.Type使Decodable.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,因为提供不可分解的类型可能是程序员的错误。