使协议可编码并将其存储在数组中

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键中。你可以选择任何你想要的名字。 感谢您的解释。没有任何想法。

以上是关于使协议可编码并将其存储在数组中的主要内容,如果未能解决你的问题,请参考以下文章

如何在可编码结构中使用计算属性(swift)

可编码为 CKRecord

如何在目标 c 数据模型类中使用 Codable 协议?

将对象转换为可编码对象失败:“ThemeData”实例

找不到在 Vapor 3 中使连接查询结果可编码的方法

如何为可编码对象提供自定义代码功能