I'm trying to encode an object and i have some troubles. It work's fine with strings, booleans and else, but i don't know how to use it for enum. I need to encode this:
enum Creature: Equatable {
enum UnicornColor {
case yellow, pink, white
}
case unicorn(UnicornColor)
case crusty
case shark
case dragon
I'm using this code for encode:
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
}
Here is required functions (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")
}
It works fine with "name", i think the problem is in aCoder.encode() coz i don't know what type to write there. I get next error when run: -[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance -[NSKeyedArchiver dealloc]: warning: NSKeyedArchiver deallocated without having had -finishEncoding called on it.
I read some advices in comments and can assume that i have no rawValues in enum Creature, i made raw type "String" in that enum:
enum Creature: String, Equatable {
enum UnicornColor {
case yellow, pink, white
}
case unicorn(UnicornColor)
case crusty
case shark
case dragon
Now i have this error: Enum with raw type cannot have cases with arguments. Also i read that associated values and raw values can't coexist. Maybe there is some other way to archive enum without raw values?
Hope someone can help me, thank's
The main problem for your issue is that you cannot pass Swift enums to encode(_:forKey:)
.
This article shown by Paulw11 will help you solve this part. If the enum can easily have rawValue
, it's not too difficult.
But, as you see, Enum with raw type cannot have cases with arguments.
Simple enums can easily have rawValue
like this:
enum UnicornColor: Int {
case yellow, pink, white
}
But enums with associate values, cannot have rawValue
in this way. You may need to manage by yourself.
For example, with having inner enum's rawValue
as 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 {
//...
}
}
You can write an extension for Dream.Creature
as:
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
}
}
}
(In fact, it is not an actual rawValue
and you'd better rename it for a more appropriate name.)
With a definition like shown above, you can utilize the code shown in the link above.
You are dealing with a problem that arises because Swift native features don't always play well with Objective-C. NSCoding
has its roots in the Objective-C world, and Objective-C doesn't know anything about Swift Enums, so you can't simply archive an Enum.
Normally, you could just encode/decode the enumeration using raw values, but as you found, you can't combine associated types and raw values in a Swift enumeration.
Unfortunately this means that you will need to build your own 'raw' value methods and handle the cases explicitly in the Creature
enumeration:
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)
}
}
}
You can then encode/decode like this:
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")
}
}
}
Testing gives:
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)
Personally, I would make Creature
a class and use inheritance to deal with the variation between unicorns and other types
To simplify the coding/decoding you could provide an initializer for Creature requiring a Data
and a computed property named data
. As Creature changes or as new associated values are added, the interface to NSCoding
does not change.
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")
}
}
A serialization of Creature into and out of Data
could be accomplished like this.
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
}
}
来源:https://stackoverflow.com/questions/43033911/how-to-archive-enum-with-an-associated-value