2017-03-26 5 views
1

Ich versuche ein Objekt zu kodieren und habe einige Probleme. Es funktioniert gut mit Strings, Booleans und anderen, aber ich weiß nicht, wie man es für enum verwendet. Ich brauche dies zu kodieren:Wie archiviere enum mit einem zugehörigen Wert?

enum Creature: Equatable { 
    enum UnicornColor { 
     case yellow, pink, white 
    } 

    case unicorn(UnicornColor) 
    case crusty 
    case shark 
    case dragon 

ich diesen Code für kodieren bin mit:

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 
} 

Hier ist erforderlich, Funktionen (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") 
} 

Es funktioniert gut mit "name", ich denke, das Problem ist in aCoder.encode() coz ich weiß nicht, welcher Typ dort zu schreiben. Ich bekomme nächsten Fehler beim Ausführen: - [_ SwiftValue encodeWithCoder:]: unerkannter Selektor an Instanz gesendet - [NSKeyedArchiver Dealloc]: Warnung: NSKeyedArchiver freigegeben, ohne -finishEncoding aufgerufen haben.

Ich las einige Hinweise in den Kommentaren und davon ausgehen können, dass ich keine RawValues ​​in Enum Kreatur haben, i roh Typ "String" in diesem Enum gemacht:

enum Creature: String, Equatable { 
    enum UnicornColor { 
     case yellow, pink, white 
    } 

    case unicorn(UnicornColor) 
    case crusty 
    case shark 
    case dragon 

Jetzt habe ich diesen Fehler: Enum mit rohem Typ kann keine Fälle mit Argumenten enthalten. Ich habe auch gelesen, dass verknüpfte Werte und Rohwerte nicht koexistieren können. Vielleicht gibt es eine andere Möglichkeit, enum ohne rohe Werte zu archivieren?

Hoffnung jemand kann mir helfen, dankt die

+0

Unrelated zu Ihrem Problem, aber mit 'NSHomeDirectory(). Anhängt ("/ Dokumente/favCreature.bin")' Ihr Weg ist falsch zu bauen. Suchen Sie nach 'documentDirectory', um viele Beispiele zu finden. – rmaddy

+0

Danke dafür! –

+0

Sie müssen einen 'rawValue' mit Ihrer Enumeration und encore/decodieren mit dem verwenden – Paulw11

Antwort

1

Das Hauptproblem für Ihr Problem ist, dass Sie nicht Swift Aufzählungen zu encode(_:forKey:) passieren können.

This article von Paulw11 gezeigt wird Ihnen helfen, diesen Teil zu lösen. Wenn das Enum leicht rawValue haben kann, ist es nicht zu schwierig.

Aber wie Sie sehen, Enum mit Raw-Typ kann nicht Fälle mit Argumenten haben.

Einfache Aufzählungen können leicht rawValue wie diese:

enum UnicornColor: Int { 
     case yellow, pink, white 
    } 

Aber Aufzählungen mit assoziierten Werten, nicht rawValue auf diese Weise haben kann. Sie müssen möglicherweise selbst verwalten.

Zum Beispiel mit mit innerer Enum der rawValue als 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 { 
     //... 
    } 
} 

Sie können für Dream.Creature eine Erweiterung schreiben wie:

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 der Tat, es ist nicht ein tatsächliches rawValue sind und Sie wähle es besser für einen passenderen Namen.)

Mit einer Definition wie oben gezeigt, kannst du den Code verwenden im obigen Link gezeigt.

+0

Vielen Dank für Ihre Antwort und für Ihre Hilfe! –

2

Sie haben es mit einem Problem zu tun, das entsteht, weil die nativen Swift-Funktionen nicht immer gut mit Objective-C funktionieren.NSCoding hat seine Wurzeln in der Objective-C-Welt, und Objective-C weiß nichts über Swift Enums, so dass Sie ein Enum nicht einfach archivieren können.

Normalerweise können Sie die Enumeration nur mithilfe von Raw-Werten codieren/decodieren. Wie Sie jedoch feststellen konnten, können Sie verknüpfte Typen und Rohwerte in einer Swift-Enumeration nicht kombinieren.

Leider bedeutet dies, dass Sie benötigen, um Ihre eigene ‚raw‘ Wert Methoden zu bauen und die Fälle explizit in der Creature Aufzählung handhaben:

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) 
     } 
    } 
} 

Sie können dann Encoder/Decoder wie folgt aus:

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 gibt:

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)

Persönlich würde ich Creature eine Klasse machen und Vererbung mit der Variation zwischen Einhörner zu behandeln und andere Arten

+0

Danke für Hilfe und für Code! –

1

die Codierung zu vereinfachen/Decodierung Sie einen Initialisierer für Creature bieten könnte eine Data und eine berechnete Eigenschaft namens data erforderlich . Wenn sich Kreatur ändert oder wenn neue assoziierte Werte hinzugefügt werden, ändert sich die Schnittstelle zu NSCoding nicht.

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") 
    } 
} 

Eine Serialisierung von Kreatur in und aus Data könnte wie folgt erreicht werden.

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

enter image description here

+0

Sehr schöne Option! Vielen Dank! –