如何存档具有关联值的枚举?
How to archive enum with an associated value?
我正在尝试对一个对象进行编码,但遇到了一些麻烦。它适用于字符串、布尔值和其他,但我不知道如何将它用于枚举。
我需要对此进行编码:
enum Creature: Equatable {
enum UnicornColor {
case yellow, pink, white
}
case unicorn(UnicornColor)
case crusty
case shark
case dragon
我正在使用此代码进行编码:
func saveFavCreature(creature: Dream.Creature) {
let filename = NSHomeDirectory().appending("/Documents/favCreature.bin")
NSKeyedArchiver.archiveRootObject(creature, toFile: filename)
}
func loadFavCreature() -> Dream.Creature {
let filename = NSHomeDirectory().appending("/Documents/favCreature.bin")
let unarchived = NSKeyedUnarchiver.unarchiveObject(withFile: filename)
return unarchived! as! Dream.Creature
}
这是必需的函数(model.favoriteCreature == Dream.Creature)
override func encode(with aCoder: NSCoder) {
aCoder.encode(model.favoriteCreature, forKey: "FavoriteCreature")
aCoder.encode(String(), forKey: "CreatureName")
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let favoriteCreature = aDecoder.decodeObject(forKey: "FavoriteCreature")
let name = aDecoder.decodeObject(forKey: "CreatureName")
}
它与 "name" 一起工作正常,我认为问题出在 aCoder.encode() 因为我不知道在那里写什么类型。 运行 时出现下一个错误:
-[_SwiftValue encodeWithCoder:]: 无法识别的选择器发送到实例
-[NSKeyedArchiver dealloc]: 警告:NSKeyedArchiver 在没有调用 -finishEncoding 的情况下被释放。
我在评论中阅读了一些建议,可以假设我在枚举生物中没有原始值,我在该枚举中创建了原始类型 "String":
enum Creature: String, Equatable {
enum UnicornColor {
case yellow, pink, white
}
case unicorn(UnicornColor)
case crusty
case shark
case dragon
现在我有这个错误:
原始类型的枚举不能有带参数的情况。
我还读到关联值和原始值不能共存。也许还有其他方法可以在没有原始值的情况下存档枚举?
希望有人能帮助我,谢谢
您的问题的主要问题是您无法将 Swift 枚举传递给 encode(_:forKey:)
。
Paulw11 展示的This article 将帮助您解决这部分问题。如果enum能轻松有rawValue
,也不算太难
但是,如您所见,原始类型的枚举不能有带参数的情况。
简单的枚举很容易有 rawValue
这样的:
enum UnicornColor: Int {
case yellow, pink, white
}
但是具有关联值的枚举不能以这种方式具有 rawValue
。您可能需要自行管理。
例如,将内部枚举的 rawValue
作为 Int
:
enum Creature: Equatable {
enum UnicornColor: Int {
case yellow, pink, white
}
case unicorn(UnicornColor)
case crusty
case shark
case dragon
static func == (lhs: Creature, rhs: Creature) -> Bool {
//...
}
}
您可以将 Dream.Creature
的扩展写为:
extension Dream.Creature: RawRepresentable {
var rawValue: Int {
switch self {
case .unicorn(let color):
return 0x0001_0000 + color.rawValue
case .crusty:
return 0x0002_0000
case .shark:
return 0x0003_0000
case .dragon:
return 0x0004_0000
}
}
init?(rawValue: Int) {
switch rawValue {
case 0x0001_0000...0x0001_FFFF:
if let color = UnicornColor(rawValue: rawValue & 0xFFFF) {
self = .unicorn(color)
} else {
return nil
}
case 0x0002_0000:
self = .crusty
case 0x0003_0000:
self = .shark
case 0x0004_0000:
self = .dragon
default:
return nil
}
}
}
(事实上,它不是真正的rawValue
,你最好将它重命名为更合适的名称。)
有了如上所示的定义,您可以使用上面 link 中显示的代码。
您正在处理因 Swift 本机功能并不总是与 Objective-C 配合良好而出现的问题。 NSCoding
起源于 Objective-C 世界,而 Objective-C 对 Swift 枚举一无所知,因此您不能简单地归档枚举。
通常,您可以 encode/decode 使用原始值的枚举,但正如您发现的那样,您不能在 Swift 枚举中组合关联类型和原始值。
不幸的是,这意味着您需要构建自己的 'raw' 值方法并在 Creature
枚举中显式处理这些情况:
enum Creature {
enum UnicornColor: Int {
case yellow = 0, pink, white
}
case unicorn(UnicornColor)
case crusty
case shark
case dragon
init?(_ creatureType: Int, color: Int? = nil) {
switch creatureType {
case 0:
guard let rawColor = color,
let unicornColor = Creature.UnicornColor(rawValue:rawColor) else {
return nil
}
self = .unicorn(unicornColor)
case 1:
self = .crusty
case 2:
self = .shark
case 3:
self = .dragon
default:
return nil
}
}
func toRawValues() -> (creatureType:Int, unicornColor:Int?) {
switch self {
case .unicorn(let color):
let rawColor = color.rawValue
return(0,rawColor)
case .crusty:
return (1,nil)
case .shark:
return (2,nil)
case .dragon:
return (3,nil)
}
}
}
然后您可以 encode/decode 像这样:
class SomeClass: NSObject, NSCoding {
var creature: Creature
init(_ creature: Creature) {
self.creature = creature
}
required init?(coder aDecoder: NSCoder) {
let creatureType = aDecoder.decodeInteger(forKey: "creatureType")
let unicornColor = aDecoder.decodeInteger(forKey: "unicornColor")
guard let creature = Creature(creatureType, color: unicornColor) else {
return nil
}
self.creature = creature
super.init()
}
func encode(with aCoder: NSCoder) {
let creatureValues = self.creature.toRawValues()
aCoder.encode(creatureValues.creatureType, forKey: "creatureType")
if let unicornColor = creatureValues.unicornColor {
aCoder.encode(unicornColor, forKey: "unicornColor")
}
}
}
测试给出:
let a = SomeClass(.unicorn(.pink))
var data = NSMutableData()
let coder = NSKeyedArchiver(forWritingWith: data)
a.encode(with: coder)
coder.finishEncoding()
let decoder = NSKeyedUnarchiver(forReadingWith: data as Data)
if let b = SomeClass(coder: decoder) {
print(b.creature)
}
unicorn(Creature.UnicornColor.pink)
就我个人而言,我会将 Creature
设为 class 并使用继承来处理独角兽与其他类型之间的差异
为了简化 coding/decoding,您可以为需要 Data
和名为 data
的计算 属性 的 Creature 提供初始化器。当 Creature 发生变化或添加新的关联值时,NSCoding
的界面不会改变。
class Foo: NSObject, NSCoding {
let creature: Creature
init(with creature: Creature = Creature.crusty) {
self.creature = creature
super.init()
}
required init?(coder aDecoder: NSCoder) {
guard let data = aDecoder.decodeObject(forKey: "creature") as? Data else { return nil }
guard let _creature = Creature(with: data) else { return nil }
self.creature = _creature
super.init()
}
func encode(with aCoder: NSCoder) {
aCoder.encode(creature.data, forKey: "creature")
}
}
Creature 进出 Data
的序列化可以像这样完成。
enum Creature {
enum UnicornColor {
case yellow, pink, white
}
case unicorn(UnicornColor)
case crusty
case shark
case dragon
enum Index {
static fileprivate let ofEnum = 0 // data[0] holds enum value
static fileprivate let ofUnicornColor = 1 // data[1] holds unicorn color
}
init?(with data: Data) {
switch data[Index.ofEnum] {
case 1:
switch data[Index.ofUnicornColor] {
case 1: self = .unicorn(.yellow)
case 2: self = .unicorn(.pink)
case 3: self = .unicorn(.white)
default:
return nil
}
case 2: self = .crusty
case 3: self = .shark
case 4: self = .dragon
default:
return nil
}
}
var data: Data {
var data = Data(count: 2)
// the initializer above zero fills data, therefore serialize values starting at 1
switch self {
case .unicorn(let color):
data[Index.ofEnum] = 1
switch color {
case .yellow: data[Index.ofUnicornColor] = 1
case .pink: data[Index.ofUnicornColor] = 2
case .white: data[Index.ofUnicornColor] = 3
}
case .crusty: data[Index.ofEnum] = 2
case .shark: data[Index.ofEnum] = 3
case .dragon: data[Index.ofEnum] = 4
}
return data
}
}
我正在尝试对一个对象进行编码,但遇到了一些麻烦。它适用于字符串、布尔值和其他,但我不知道如何将它用于枚举。 我需要对此进行编码:
enum Creature: Equatable {
enum UnicornColor {
case yellow, pink, white
}
case unicorn(UnicornColor)
case crusty
case shark
case dragon
我正在使用此代码进行编码:
func saveFavCreature(creature: Dream.Creature) {
let filename = NSHomeDirectory().appending("/Documents/favCreature.bin")
NSKeyedArchiver.archiveRootObject(creature, toFile: filename)
}
func loadFavCreature() -> Dream.Creature {
let filename = NSHomeDirectory().appending("/Documents/favCreature.bin")
let unarchived = NSKeyedUnarchiver.unarchiveObject(withFile: filename)
return unarchived! as! Dream.Creature
}
这是必需的函数(model.favoriteCreature == Dream.Creature)
override func encode(with aCoder: NSCoder) {
aCoder.encode(model.favoriteCreature, forKey: "FavoriteCreature")
aCoder.encode(String(), forKey: "CreatureName")
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let favoriteCreature = aDecoder.decodeObject(forKey: "FavoriteCreature")
let name = aDecoder.decodeObject(forKey: "CreatureName")
}
它与 "name" 一起工作正常,我认为问题出在 aCoder.encode() 因为我不知道在那里写什么类型。 运行 时出现下一个错误: -[_SwiftValue encodeWithCoder:]: 无法识别的选择器发送到实例 -[NSKeyedArchiver dealloc]: 警告:NSKeyedArchiver 在没有调用 -finishEncoding 的情况下被释放。
我在评论中阅读了一些建议,可以假设我在枚举生物中没有原始值,我在该枚举中创建了原始类型 "String":
enum Creature: String, Equatable {
enum UnicornColor {
case yellow, pink, white
}
case unicorn(UnicornColor)
case crusty
case shark
case dragon
现在我有这个错误: 原始类型的枚举不能有带参数的情况。 我还读到关联值和原始值不能共存。也许还有其他方法可以在没有原始值的情况下存档枚举?
希望有人能帮助我,谢谢
您的问题的主要问题是您无法将 Swift 枚举传递给 encode(_:forKey:)
。
This article 将帮助您解决这部分问题。如果enum能轻松有rawValue
,也不算太难
但是,如您所见,原始类型的枚举不能有带参数的情况。
简单的枚举很容易有 rawValue
这样的:
enum UnicornColor: Int {
case yellow, pink, white
}
但是具有关联值的枚举不能以这种方式具有 rawValue
。您可能需要自行管理。
例如,将内部枚举的 rawValue
作为 Int
:
enum Creature: Equatable {
enum UnicornColor: Int {
case yellow, pink, white
}
case unicorn(UnicornColor)
case crusty
case shark
case dragon
static func == (lhs: Creature, rhs: Creature) -> Bool {
//...
}
}
您可以将 Dream.Creature
的扩展写为:
extension Dream.Creature: RawRepresentable {
var rawValue: Int {
switch self {
case .unicorn(let color):
return 0x0001_0000 + color.rawValue
case .crusty:
return 0x0002_0000
case .shark:
return 0x0003_0000
case .dragon:
return 0x0004_0000
}
}
init?(rawValue: Int) {
switch rawValue {
case 0x0001_0000...0x0001_FFFF:
if let color = UnicornColor(rawValue: rawValue & 0xFFFF) {
self = .unicorn(color)
} else {
return nil
}
case 0x0002_0000:
self = .crusty
case 0x0003_0000:
self = .shark
case 0x0004_0000:
self = .dragon
default:
return nil
}
}
}
(事实上,它不是真正的rawValue
,你最好将它重命名为更合适的名称。)
有了如上所示的定义,您可以使用上面 link 中显示的代码。
您正在处理因 Swift 本机功能并不总是与 Objective-C 配合良好而出现的问题。 NSCoding
起源于 Objective-C 世界,而 Objective-C 对 Swift 枚举一无所知,因此您不能简单地归档枚举。
通常,您可以 encode/decode 使用原始值的枚举,但正如您发现的那样,您不能在 Swift 枚举中组合关联类型和原始值。
不幸的是,这意味着您需要构建自己的 'raw' 值方法并在 Creature
枚举中显式处理这些情况:
enum Creature {
enum UnicornColor: Int {
case yellow = 0, pink, white
}
case unicorn(UnicornColor)
case crusty
case shark
case dragon
init?(_ creatureType: Int, color: Int? = nil) {
switch creatureType {
case 0:
guard let rawColor = color,
let unicornColor = Creature.UnicornColor(rawValue:rawColor) else {
return nil
}
self = .unicorn(unicornColor)
case 1:
self = .crusty
case 2:
self = .shark
case 3:
self = .dragon
default:
return nil
}
}
func toRawValues() -> (creatureType:Int, unicornColor:Int?) {
switch self {
case .unicorn(let color):
let rawColor = color.rawValue
return(0,rawColor)
case .crusty:
return (1,nil)
case .shark:
return (2,nil)
case .dragon:
return (3,nil)
}
}
}
然后您可以 encode/decode 像这样:
class SomeClass: NSObject, NSCoding {
var creature: Creature
init(_ creature: Creature) {
self.creature = creature
}
required init?(coder aDecoder: NSCoder) {
let creatureType = aDecoder.decodeInteger(forKey: "creatureType")
let unicornColor = aDecoder.decodeInteger(forKey: "unicornColor")
guard let creature = Creature(creatureType, color: unicornColor) else {
return nil
}
self.creature = creature
super.init()
}
func encode(with aCoder: NSCoder) {
let creatureValues = self.creature.toRawValues()
aCoder.encode(creatureValues.creatureType, forKey: "creatureType")
if let unicornColor = creatureValues.unicornColor {
aCoder.encode(unicornColor, forKey: "unicornColor")
}
}
}
测试给出:
let a = SomeClass(.unicorn(.pink))
var data = NSMutableData()
let coder = NSKeyedArchiver(forWritingWith: data)
a.encode(with: coder)
coder.finishEncoding()
let decoder = NSKeyedUnarchiver(forReadingWith: data as Data)
if let b = SomeClass(coder: decoder) {
print(b.creature)
}
unicorn(Creature.UnicornColor.pink)
就我个人而言,我会将 Creature
设为 class 并使用继承来处理独角兽与其他类型之间的差异
为了简化 coding/decoding,您可以为需要 Data
和名为 data
的计算 属性 的 Creature 提供初始化器。当 Creature 发生变化或添加新的关联值时,NSCoding
的界面不会改变。
class Foo: NSObject, NSCoding {
let creature: Creature
init(with creature: Creature = Creature.crusty) {
self.creature = creature
super.init()
}
required init?(coder aDecoder: NSCoder) {
guard let data = aDecoder.decodeObject(forKey: "creature") as? Data else { return nil }
guard let _creature = Creature(with: data) else { return nil }
self.creature = _creature
super.init()
}
func encode(with aCoder: NSCoder) {
aCoder.encode(creature.data, forKey: "creature")
}
}
Creature 进出 Data
的序列化可以像这样完成。
enum Creature {
enum UnicornColor {
case yellow, pink, white
}
case unicorn(UnicornColor)
case crusty
case shark
case dragon
enum Index {
static fileprivate let ofEnum = 0 // data[0] holds enum value
static fileprivate let ofUnicornColor = 1 // data[1] holds unicorn color
}
init?(with data: Data) {
switch data[Index.ofEnum] {
case 1:
switch data[Index.ofUnicornColor] {
case 1: self = .unicorn(.yellow)
case 2: self = .unicorn(.pink)
case 3: self = .unicorn(.white)
default:
return nil
}
case 2: self = .crusty
case 3: self = .shark
case 4: self = .dragon
default:
return nil
}
}
var data: Data {
var data = Data(count: 2)
// the initializer above zero fills data, therefore serialize values starting at 1
switch self {
case .unicorn(let color):
data[Index.ofEnum] = 1
switch color {
case .yellow: data[Index.ofUnicornColor] = 1
case .pink: data[Index.ofUnicornColor] = 2
case .white: data[Index.ofUnicornColor] = 3
}
case .crusty: data[Index.ofEnum] = 2
case .shark: data[Index.ofEnum] = 3
case .dragon: data[Index.ofEnum] = 4
}
return data
}
}