【问题标题】: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 我先试过这个,忘了说。我将更新帖子,说明为什么它不是一个完美的解决方案以及为什么我会看到是否还有其他问题。谢谢

标签: swift codable decodable encodable


【解决方案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键中。你可以选择任何你想要的名字。
    • 感谢您的解释。没有任何想法。
    猜你喜欢
    • 2011-02-11
    • 1970-01-01
    • 2020-02-09
    • 2016-10-22
    • 1970-01-01
    • 1970-01-01
    • 2019-02-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多