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

时间:2018-05-06 23:19:02

标签: swift codable decodable encodable

我有一个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!)以删除初始化程序的要求。但还有其他方法吗?这似乎是一个简单的问题,但它的工作使它非常混乱,我不喜欢。此外,当我使用此方法从文件保存/加载时,似乎没有保存数据

2 个答案:

答案 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
    }
}