我的应用程序具有本地缓存,并从/向服务器发送/接收模型.所以我决定构建一个map [String:Codable.Type],基本上能够解码我在本地创建或从服务器接收的通用缓存上的任何内容.
let encoder = JSONEncoder() let decoder = JSONDecoder() var modelNametoType = [String : Codable.Type]() modelNametoType = ["ContactModel": ContactModel.Self,"AnythingModel" : AnythingModel.Self,...]
无论我在App上创建什么,我都可以成功编码并存储在缓存中,如下所示:
let contact = ContactModel(name: "John") let data = try! encoder.encode(contact) CRUD.shared.storekey(key: "ContactModel$10",contact)
我想像这样解码:
let result = try! decoder.decode(modelNametoType["ContactModel"]!,from: data)
但我得到错误:
Cannot invoke ‘decode’ with an argument list of type (Codable.Type,
from: Data)
我究竟做错了什么?任何帮助表示赞赏
let result = try! decoder.decode(ContactModel.self,from: data)
联系型号:
struct ContactModel: Codable { var name : String }
对于远程请求,我会有这样的函数:
func buildAnswer(keys: [String]) -> Data { var result = [String:Codable]() for key in keys { let data = CRUD.shared.restoreKey(key: key) let item = try decoder.decode(modelNametoType[key]!,from: data) result[key] = item } return try encoder.encode(result) }
…如果我解决了解码问题.任何帮助赞赏.
解决方法
Codable API围绕编码和解码为具体类型而构建.但是,你想要的往返不应该知道任何具体的类型;它只是将异构JSON值连接成一个JSON对象.
因此,在这种情况下,JSONSerialization是一个更好的工具,因为它处理Any:
import Foundation // I would consider lifting your String keys into their own type btw. func buildAnswer(keys: [String]) throws -> Data { var result = [String: Any](minimumCapacity: keys.count) for key in keys { let data = CRUD.shared.restoreKey(key: key) result[key] = try JSONSerialization.jsonObject(with: data) } return try JSONSerialization.data(withJSONObject: result) }
话虽如此,您仍然可以使用JSONDecoder / JSONEncoder进行此操作 – 但它需要相当多的类型擦除样板.
例如,我们需要一个符合encodable的包装类型,如encodable doesn’t conform to itself:
import Foundation struct AnyCodable : encodable { private let _encode: (Encoder) throws -> Void let base: Codable let codableType: AnyCodableType init<Base : Codable>(_ base: Base) { self.base = base self._encode = { var container = $0.singleValueContainer() try container.encode(base) } self.codableType = AnyCodableType(type(of: base)) } func encode(to encoder: Encoder) throws { try _encode(encoder) } }
我们还需要一个包装器来捕获可用于解码的具体类型:
struct AnyCodableType { private let _decodeJSON: (JSONDecoder,Data) throws -> AnyCodable // repeat for other decoders... // (unfortunately I don't believe there's an easy way to make this generic) // let base: Codable.Type init<Base : Codable>(_ base: Base.Type) { self.base = base self._decodeJSON = { decoder,data in AnyCodable(try decoder.decode(base,from: data)) } } func decode(from decoder: JSONDecoder,data: Data) throws -> AnyCodable { return try _decodeJSON(decoder,data) } }
我们不能简单地将Decodable.Type传递给JSONDecoder
func decode<T : Decodable>(_ type: T.Type,from data: Data) throws -> T
当T是协议类型时,type:参数采用.Protocol元类型,而不是.Type元类型(有关详细信息,请参阅this Q&A).
我们现在可以为我们的键定义一个类型,使用一个modelType属性返回一个可以用来解码JSON的AnyCodableType:
enum ModelName : String { case contactModel = "ContactModel" case anythingModel = "AnythingModel" var modelType: AnyCodableType { switch self { case .contactModel: return AnyCodableType(ContactModel.self) case .anythingModel: return AnyCodableType(AnythingModel.self) } } }
然后为往返做这样的事情:
func buildAnswer(keys: [ModelName]) throws -> Data { let decoder = JSONDecoder() let encoder = JSONEncoder() var result = [String: AnyCodable](minimumCapacity: keys.count) for key in keys { let rawValue = key.rawValue let data = CRUD.shared.restoreKey(key: rawValue) result[rawValue] = try key.modelType.decode(from: decoder,data: data) } return try encoder.encode(result) }
这可能更好地设计为使用Codable而不是它(可能是一个表示您发送到服务器的JSON对象的结构,并使用键路径与缓存层交互),但不知道更多有关CRUD.shared和你如何使用它;很难说.