我有动物协议,其中包含 2 个符合它的结构和一个存储动物列表的农场结构。然后,我使它们都符合 Codable 以将其存储在文件中,但它抛出错误cannot automatically synthesize 'Encodable' because '[Animal]' does not conform to 'Encodable'
我理解为什么会发生这种情况,但我找不到一个好的解决方案。如何使数组仅接受可编码和动物,而不将动物标记为可编码,这样就不会发生此问题,例如var animals = [Codable & Animal]
?(或任何其他解决方法)。谢谢
protocol Animal: Codable {
var name: String { get set }
var sound: String { get set }
}
struct Cow: Animal {
var name = "Cow"
var sound = "Moo!"
}
struct Duck: Animal {
var name = "Duck"
var sound = "Quack!"
}
struct Farm: Codable {
var name = "Manor Farm"
// this is where the error is shown
var animals = [Animal]()
}
--编辑-- 当我将它们更改为类时,它看起来像这样:
class Animal: Codable {
var name = ""
var sound = ""
}
class Duck: Animal {
var beakLength: Int
init(beakLength: Int) {
self.beakLength = beakLength
super.init()
name = "Duck"
sound = "Quack!"
}
required init(from decoder: Decoder) throws {
// works, but now I am required to manually do this?
fatalError("init(from:) has not been implemented")
}
}
如果我没有其他属性,它会起作用,但是一旦我添加一个,我就需要引入一个初始值设定项,然后这需要我包含来自解码器初始化器的 init 初始化器,该初始值设定项删除了 Codable 提供的自动转换。因此,要么我为我扩展的每个类手动执行此操作,要么我可以强制强制转换变量(如var beakLength: Int!
)以删除初始值设定项的要求。但是还有其他方法吗?这似乎是一个简单的问题,但是解决方法使它非常混乱,我不喜欢。另外,当我使用此方法从文件保存/加载时,似乎没有保存数据
您可以通过两种方式执行此操作:
1 解决方案 - 使用包装器:
protocol Animal {}
struct Cow: Animal, Codable {
}
struct Duck: Animal, Codable {
}
struct Farm: Codable {
let animals: [Animal]
private enum CodingKeys: String, CodingKey {
case animals
}
func encode(to encoder: Encoder) throws {
let wrappers = animals.map { AnimalWrapper($0) }
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(wrappers, forKey: .animals)
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let wrappers = try container.decode([AnimalWrapper].self, forKey: .animals)
self.animals = wrappers.map { $0.animal }
}
}
fileprivate struct AnimalWrapper: Codable {
let animal: Animal
private enum CodingKeys: String, CodingKey {
case base, payload
}
private enum Base: Int, Codable {
case cow
case duck
}
init(_ animal: Animal) {
self.animal = animal
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let base = try container.decode(Base.self, forKey: .base)
switch base {
case .cow:
self.animal = try container.decode(Cow.self, forKey: .payload)
case .duck:
self.animal = try container.decode(Duck.self, forKey: .payload)
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch animal {
case let payload as Cow:
try container.encode(Base.cow, forKey: .base)
try container.encode(payload, forKey: .payload)
case let payload as Duck:
try container.encode(Base.duck, forKey: .base)
try container.encode(payload, forKey: .payload)
default:
break
}
}
}
2 解决方案 - 使用枚举
struct Cow: Codable {
}
struct Duck: Codable {
}
enum Animal {
case cow(Cow)
case duck(Duck)
}
extension Animal: Codable {
private enum CodingKeys: String, CodingKey {
case base, payload
}
private enum Base: Int, Codable {
case cow
case duck
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let base = try container.decode(Base.self, forKey: .base)
switch base {
case .cow:
self = .cow(try container.decode(Cow.self, forKey: .payload))
case .duck:
self = .duck(try container.decode(Duck.self, forKey: .payload))
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .cow(let payload):
try container.encode(Base.cow, forKey: .base)
try container.encode(payload, forKey: .payload)
case .duck(let payload):
try container.encode(Base.duck, forKey: .base)
try container.encode(payload, forKey: .payload)
}
}
}
就个人而言,我会选择@nightwill枚举解决方案。这似乎是正确的做法。但是,如果您确实需要编码和解码一些您不拥有的协议约束对象,这里有一种方法:
protocol Animal {
var name: String { get set }
var sound: String { get set }
//static var supportedTypes : CodingUserInfoKey { get set }
}
typealias CodableAnimal = Animal & Codable
struct Cow: CodableAnimal {
var name = "Cow"
var sound = "Moo!"
var numberOfHorns : Int = 2 // custom property
// if you don't add any custom non optional properties you Cow can easyly be decoded as Duck
}
struct Duck: CodableAnimal {
var name = "Duck"
var sound = "Quack!"
var wingLength: Int = 50 // custom property
}
struct Farm: Codable {
var name = "Manor Farm"
var animals = [Animal]()
enum CodingKeys: String, CodingKey {
case name
case animals
}
func encode(to encoder: Encoder) throws {
var c = encoder.container(keyedBy: CodingKeys.self)
try c.encode(name, forKey: .name)
var aniC = c.nestedUnkeyedContainer(forKey: .animals)
for a in animals {
if let duck = a as? Duck {
try aniC.encode(duck)
} else if let cow = a as? Cow {
try aniC.encode(cow)
}
}
}
init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
name = try c.decode(String.self, forKey: .name)
var aniC = try c.nestedUnkeyedContainer(forKey: .animals)
while !aniC.isAtEnd {
if let duck = try? aniC.decode(Duck.self) {
animals.append(duck)
} else if let cow = try? aniC.decode(Cow.self) {
animals.append(cow)
}
}
}
init(name: String, animals: [Animal]) {
self.name = name
self.animals = animals
}
}
游乐场快速检查:
let farm = Farm(name: "NewFarm", animals: [Cow(), Duck(), Duck(), Duck(name: "Special Duck", sound: "kiya", wingLength: 70)])
print(farm)
import Foundation
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
let encodedData = try! jsonEncoder.encode(farm)
print(String(data: encodedData, encoding: .utf8)!)
if let decodedFarm = try? JSONDecoder().decode(Farm.self, from: encodedData) {
print(decodedFarm)
let encodedData2 = try! jsonEncoder.encode(decodedFarm)
print(String(data: encodedData2, encoding: .utf8)!)
assert(encodedData == encodedData2)
} else {
print ("Failed somehow")
}
下面是使用属性包装器的附加方法。此方法与基于类的模型高度兼容。但是,对于结构,必须使用以下代码片段注册它们:TypeHelper.register(type: MyTypeModel.self)
import Foundation
protocol MainCodable: Codable {}
extension MainCodable {
static var typeName: String { String(describing: Self.self) }
var typeName: String { Self.typeName }
}
/// Convert string to type. didn't find way to convert non reference types from string
/// You can register any type by using register function
struct TypeHelper {
private static var availableTypes: [String: Any.Type] = [:]
private static var module = String(reflecting: TypeHelper.self).components(separatedBy: ".")[0]
static func typeFrom(name: String) -> Any.Type? {
if let type = availableTypes[name] {
return type
}
return _typeByName("(module).(name)")
}
static func register(type: Any.Type) {
availableTypes[String(describing: type)] = type
}
}
@propertyWrapper
struct AnyMainCodable<T>: Codable, CustomDebugStringConvertible {
private struct Container: Codable, CustomDebugStringConvertible {
let data: MainCodable
enum CodingKeys: CodingKey {
case className
}
init?(data: Any) {
guard let data = data as? MainCodable else { return nil }
self.data = data
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let name = try container.decode(String.self, forKey: .className)
guard let type = TypeHelper.typeFrom(name: name) as? MainCodable.Type else {
throw DecodingError.valueNotFound(String.self, .init(codingPath: decoder.codingPath, debugDescription: "invalid type (name)"))
}
data = try type.init(from: decoder)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(data.typeName, forKey: .className)
try data.encode(to: encoder)
}
var debugDescription: String {
"(data)"
}
}
var wrappedValue: [T] {
get { containers.map { $0.data as! T } }
set { containers = newValue.compactMap({ Container(data: $0) }) }
}
private var containers: [Container]
init(wrappedValue: [T]) {
if let item = wrappedValue.first(where: { !($0 is MainCodable) }) {
fatalError("unsupported type: (type(of: item)) ((item))")
}
self.containers = wrappedValue.compactMap({ Container(data: $0) })
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
self.containers = try container.decode([Container].self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(containers)
}
var debugDescription: String {
"(wrappedValue)"
}
}
例
protocol Proto: MainCodable {
var commData: String { get }
}
class A: Proto {
var someData: Int
var commData: String
init(someData: Int, commData: String) {
self.someData = someData
self.commData = commData
}
}
class B: Proto {
var theData: String
var commData: String
init(theData: String, commData: String) {
self.theData = theData
self.commData = commData
}
}
struct C: MainCodable {
let cValue: String
init(cValue: String) {
self.cValue = cValue
}
}
// For struct need to register every struct type you have to support
TypeHelper.register(type: C.self)
struct Example: Codable {
@AnyMainCodable var data1: [Proto]
@AnyMainCodable var data2: [MainCodable]
var someOtherData: String
}
let example = Example(
data1: [A(someData: 10, commData: "my Data1"), B(theData: "20", commData: "my Data 2")],
data2: [A(someData: 30, commData: "my Data3"), C(cValue: "new value")],
someOtherData: "100"
)
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let decoder = JSONDecoder()
var data = try encoder.encode(example)
print(String(data: data, encoding: .utf8) ?? "")
print(try decoder.decode(type(of: example), from: data))
print(example.data1.map(.commData))
输出
{
"data1" : [
{
"className" : "A",
"someData" : 10,
"commData" : "my Data1"
},
{
"className" : "B",
"theData" : "20",
"commData" : "my Data 2"
}
],
"data2" : [
{
"className" : "A",
"someData" : 30,
"commData" : "my Data3"
},
{
"className" : "C",
"cValue" : "new value"
}
],
"someOtherData" : "100"
}
Example(_data1: [PlaygroundCLI.A, PlaygroundCLI.B], _data2: [PlaygroundCLI.A, PlaygroundCLI.C(cValue: "new value")], someOtherData: "100")
["my Data1", "my Data 2"]