使协议可编码并将其存储在数组中
Posted
技术标签:
【中文标题】使协议可编码并将其存储在数组中【英文标题】:Make a protocol Codable and store it in an array 【发布时间】:2018-10-16 17:43:30 【问题描述】:我有 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]()
--编辑-- 当我将它们更改为一个类时,它看起来像这样:
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")
如果我没有其他属性,它会起作用,但是一旦我添加了一个,我需要引入一个初始化程序,然后我需要从解码器初始化程序中包含初始化程序,它删除了 Codable 提供的自动转换。因此,要么我为我扩展的每个类手动执行此操作,要么我可以强制转换变量(如var beakLength: Int!
)以消除对初始化程序的要求。但是有没有别的办法?这似乎是一个简单的问题,但解决它使它变得非常混乱,我不喜欢。另外,当我使用这种方法从文件中保存/加载时,似乎没有保存数据
【问题讨论】:
如何将 Animal 协议更改为一个类,并让 Cow 和 Duck 成为它的子类 只需让 Duck 和 Cow Codable 并从 Animal 中删除 Codable 没有数组的类型是协议,你可以把Animal改成类和子类 @LeoDabus 但随后 Farm 将无法 Codable,因为一旦 Animal 不是,则不能保证数组是 Codable。 @NaderBesada 我先试过这个,忘了说。我将更新帖子,说明为什么它不是一个完美的解决方案以及为什么我会看到是否还有其他问题。谢谢 【参考方案1】:我个人会选择@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")
【讨论】:
【参考方案2】:您可以通过两种方式做到这一点:
1 解决方案 - 使用 Wrapper:
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)
【讨论】:
我错过了什么?基础和有效载荷来自哪里?payload
- 这是一个自定义 CodingKey,用于存储对象。但是要知道payload
键中存储了什么样的对象,我们应该将对象的类型存储在base
键中。你可以选择任何你想要的名字。
感谢您的解释。没有任何想法。以上是关于使协议可编码并将其存储在数组中的主要内容,如果未能解决你的问题,请参考以下文章