【问题标题】:Using CodingKeys with custom protocol将 CodingKeys 与自定义协议一起使用
【发布时间】:2022-01-16 17:22:54
【问题描述】:

我有以下 Codable 协议,其中包含一个我想从可编码协议中排除的变量。

问题是我无法在我自己的协议中使用为此创建的 CodingKeys 枚举:Type 'CodingKeys' cannot be nested in protocol 'Animal'

protocol Animal: Codable {

    var name: String { get set }
    var color: String { get }

    var selfiePicture: Selfie { get }

    // Not possible
    enum CodingKeys: String, CodingKey {
        case name
        case color
    }

}

我该如何解决这个问题?


编辑更多代码和更具体的例子

Animal 被多个结构使用(不能是类):

struct Frog: Animal {
    var name: String
    var color: String

    // extra variables on top of Animal's ones
    var isPoisonous: Bool

    var selfiePicture = [...]
}

它也被用作另一个顶级编码对象上的变量数组:

final class Farm: Codable {

    var address: String
    // more variables
    var animals: [Animal]

    enum CodingKeys: String, CodingKey {
        case address
        case animals
    }

    convenience init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)

        address = try values.decode(String.self, forKey: .address)
        animals = try values.decode([Animal].self, forKey: .animals)   // ERROR --> Protocol 'Animal' as a type cannot conform to 'Decodable'
    }
}

【问题讨论】:

  • 在这里查看协议中的嵌套类型:stackoverflow.com/questions/31845066/…
  • 如果您使用class 并手动实现Codable,则可以共享密钥。那么任何其他共享的类都可以使用键继承class
  • @Iorem 我不能,因为 MyProtocol 被结构而不是类使用。
  • 为了更精确,我编辑了我的问题。
  • 您不需要协议,使用struct Animal 并添加一个属性type,它可以是您所有类型动物的枚举

标签: swift enums protocols codable


【解决方案1】:

解决此问题的一种方法是使用组合,将公共属性移动到新类型并在协议中使用该类型。

所以让我们为公共属性创建一个类型,并让该类型保存CodingKey 枚举

struct AnimalCommon: Codable {
    var name: String
    var color: String

    var selfiePicture: Selfie = Selfie()

    enum CodingKeys: String, CodingKey {
        case name
        case color
    }
}

协议变成了

protocol Animal: Codable {
    var common: AnimalCommon { get set }
}

之后就很容易实现实际的 Animal 类型,例如

struct Frog: Animal {
    var common: AnimalCommon
    var isPoisonous: Bool
}

let frog = Frog(common: AnimalCommon(name: "kermit", color: "green"), isPoisonous: false)
do {
    let data = try JSONEncoder().encode(frog)

    if let string = String(data: data, encoding: .utf8) { print(frog) }
} catch {
    print(error)
}

您还可以使用计算属性向协议添加扩展,以便您可以直接访问属性,即frog.name = "Kermit"

extension Animal {
    var name: String {
        get {
            common.name
        }
        set {
            common.name = newValue
        }
    }

    var color: String {
        common.color
    }
}

【讨论】:

  • 问题是我使用了另一个包含var protocolStructs: [MyProtocol]的最终类中的MyProtocol,它需要MyProtocol为Codable
  • @Tulleb 但是 MyProtocol 符合 Codable
  • 解码protocolStructs 时出现错误Protocol 'MyProtocol' as a type cannot conform to 'Decodable'。我将用更多信息编辑我的问题。
  • 感谢您的编辑@JoakimDanielson,我现在正在尝试。看起来很棒!
  • 看起来 Swift 不想解码协议:我仍然有 Protocol 'Animal' as a type cannot conform to 'Decodable' 错误(animals = try values.decode([Animal].self, forKey: .animals) 行)。
【解决方案2】:

Protocol type cannot conform to protocol because only concrete types can conform to protocols 之后,我不得不放弃protocol 并在内部使用struct + enum

尽管@JoakimDanielson 的回答很有希望,但它并不能解决我在尝试从我的Farm 类解码Animal 数组时遇到的错误:Protocol 'Animal' as a type cannot conform to 'Decodable'

这是我的模型最后的样子:

struct Animal: Codable {

    enum Species: Codable {
        case frog(FrogSpecificities)
        case ...

        var selfiePicture: Selfie {
            switch self {
            case .frog(let frogSpecificities):
                return frogSpecificities.selfie
            case ...
                ...
            }
        }

        enum CodingKeys: String, CodingKey {
            case FrogSpecificities
            case ...
        }

        init(from decoder: Decoder) throws {
            let values = try decoder.container(keyedBy: CodingKeys.self)

            if let frogSpecificities = try? values.decode(FrogSpecificities.self, forKey: .frogSpecificities) {
                self = .frog(frogSpecificities)
            } else if ...
                ...
            } else {
                // throw an error here if no case was decodable
            }
        }

        func encode(to encoder: Encoder) throws {
            var container = encoder.container(keyedBy: CodingKeys.self)

            switch self {
            case .frog(let frogSpecificities):
                try container.encode(frogSpecificities, forKey: .frogSpecificities)
            case ...:
                ...
            }
        }
    }

    var name: String
    let color: String

    var species: Species

    enum CodingKeys: String, CodingKey {
        case name
        case color
        case specificities
    }

}

struct FrogSpecificities: Codable {

    let isPoisonous: Bool
    let selfie = Selfie()

    enum CodingKeys: String, CodingKey {
        case isPoisonous
    }

}

final class Farm: Codable {

    var address: String
    var animals: [Animal]

    enum CodingKeys: String, CodingKey {
        case address
        case animals
    }

    convenience init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)

        address = try values.decode(String.self, forKey: .address)
        animals = try values.decode([Animal].self, forKey: .animals) // Works fine
    }
}

我的Farm 对象现在可以包含一个Animal 数组,其中每个数组都有特定的可编码struct。它还可以包含不可编码的变量。 我可以像这样访问我的动物的每个特性:

if let firstAnimel = MyFarm.animals.firstObject,
    case .frog(let frogSpecificities) = firstAnimal.species {
    print(frogSpecificities.isPoisonous)
}

【讨论】:

    猜你喜欢
    • 2019-05-20
    • 2020-10-20
    • 2015-11-10
    • 2011-12-11
    • 1970-01-01
    • 1970-01-01
    • 2020-07-10
    • 1970-01-01
    • 2018-10-30
    相关资源
    最近更新 更多