我有一个Animal协议,它有2个符合它的结构和一个存储动物列表的Farm结构。然后,我让它们都符合Codable将它存储在一个文件中,但它会引发错误cannot automatically synthesize 'Encodable' because '[Animal]' does not conform to 'Encodable'
我理解为什么会这样,但我找不到一个好的解决方案。如何让数组只接受Codable和Animal,而不将Animal标记为Codable,这样就不会出现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]()
}
- edit-- 当我将它们更改为类时,它看起来像这样:
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!
)以删除初始化程序的要求。但还有其他方法吗?这似乎是一个简单的问题,但它的工作使它非常混乱,我不喜欢。此外,当我使用此方法从文件保存/加载时,似乎没有保存数据
答案 0 :(得分:2)
您可以通过两种方式执行此操作:
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)
}
}
}
答案 1 :(得分:0)
我个人会选择@nightwill枚举解决方案。这似乎是正确的做法。但是,如果您确实需要对自己不拥有的某些协议约束对象进行编码和解码,则可以采用以下方法:
const path = require('path')
var notarize = require('electron-notarize')
module.exports = async function (params) {
// Notarization only applies to macOS
if (process.platform !== 'darwin') {
return
}
let appId = '<your-app-id>'
let appPath = path.join(
params.appOutDir,
`${params.packager.appInfo.productFilename}.app`
)
try {
console.log(` • Notarizing`)
await notarize.notarize({
appBundleId: appId,
appPath: appPath,
appleId: process.env.APPLE_ID_EMAIL,
appleIdPassword: process.env.APPLE_ID_PASSWORD,
})
} catch (error) {
console.error(error)
}
}
游乐场快速检查:
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
}
}