Ren*_*905 5 generics casting type-erasure swift codable
我正在尝试编写一个通用函数来解析几种不同的数据类型。
最初这个方法只适用于 Codable 类型,所以它的泛型类型被约束,<T: Codable>一切都很好。不过现在,我正在尝试扩展它以检查返回类型是否为 Codable,并根据该检查相应地解析数据
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
}
}
Run Code Online (Sandbox Code Playgroud)
所以,你可以看到类型检查工作正常,但我被困在得到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(:)执行。
那么,澄清这个问题的症结所在:是否可以有条件地/可选地将类型信息(例如协议一致性)添加回已擦除的类型?换句话说,一旦一个类型变成了<T>,有没有办法将它转换为<T: Decodable>?
您可以有条件地转换T.self为Decodable.Type以获得描述底层Decodable一致类型的元类型:
switch T.self {\n case let decodableType as Decodable.Type:\nRun Code Online (Sandbox Code Playgroud)\n\n但是,如果我们尝试传递decodableType给JSONDecoder,我们就会遇到问题:
// error: Cannot invoke \'decode\' with an argument list of type\n// \'(Decodable.Type, from: Data)\'\nreturn try? JSONDecoder().decode(decodableType, from: data)\nRun Code Online (Sandbox Code Playgroud)\n\n这不起作用,因为decode(_:from:)有一个通用占位符T : Decodable:
func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T \nRun Code Online (Sandbox Code Playgroud)\n\n我们不能满足T.Type,Decodable.Type因为Decodable 它不符合它本身。
正如上面链接的问答中所探讨的,此问题的一种解决方法是打开该Decodable.Type值,以便挖掘出符合Decodable\xe2\x80\x93 的底层具体类型,然后我们可以用它来满足T.
这可以通过协议扩展来完成:
\n\nextension Decodable {\n static func openedJSONDecode(\n using decoder: JSONDecoder, from data: Data\n ) throws -> Self {\n return try decoder.decode(self, from: data)\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n\n我们可以将其称为:
\n\nreturn try? (decodableType.openedJSONDecode(using: JSONDecoder(), from: data) as! T)\nRun Code Online (Sandbox Code Playgroud)\n\n您会注意到,我们必须将强制转换插入到T。这是因为通过强制转换,T.self我们Decodable.Type删除了元类型也描述类型的事实T。因此我们需要强制转换才能取回这些信息。
总而言之,您会希望您的函数看起来像这样:
\n\nfunc jsonDecode<T>(_ metatype: T.Type, from data: Data) throws -> T {\n switch metatype {\n case let codableType as Decodable.Type:\n let decoded = try codableType.openedJSONDecode(\n using: JSONDecoder(), from: data\n )\n\n // The force cast `as! T` is guaranteed to always succeed due to the fact\n // that we\'re decoding using `metatype: T.Type`. The cast to\n // `Decodable.Type` unfortunately erases this information.\n return decoded as! T\n\n case is [String: Any].Type, is [Any].Type:\n let rawDecoded = try JSONSerialization.jsonObject(with: data, options: [])\n guard let decoded = rawDecoded as? T else {\n throw DecodingError.typeMismatch(\n type(of: rawDecoded), .init(codingPath: [], debugDescription: """\n Expected to decode \\(metatype), found \\(type(of: rawDecoded)) instead.\n """)\n )\n }\n return decoded\n\n default:\n fatalError("\\(metatype) is not Decodable nor [String: Any] nor [Any]")\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n\n我还做了其他一些更改:
\n\n将返回类型从 更改T?为T。一般来说,您要么希望通过让函数返回一个可选值来处理错误,要么让它抛出 \xe2\x80\x93 来处理错误,这对于调用者来说处理这两种情况可能会非常混乱。
添加了显式参数T.Type. 这避免了依赖调用者使用返回类型推断来满足T,IMO 是类似于按返回类型重载的模式,API 设计指南不鼓励这种模式。
default:之所以这样做fatalError,是因为提供不可解码的类型可能应该是程序员的错误。