如何使用关联值存档枚举?

时间:2017-03-26 20:05:21

标签: swift enums encode nscoding

我试图编码一个对象,但我遇到了一些麻烦。它对弦乐,布尔等都很好用,但我不知道怎么用它来演奏枚举。 我需要对此进行编码:

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的情况下解除分配。

我在评论中阅读了一些建议,可以假设我在枚举生物中没有rawValues,我做了原始类型" String"在那个枚举中:

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

    case unicorn(UnicornColor)
    case crusty
    case shark
    case dragon

现在我有这个错误: 具有原始类型的枚举不能包含带参数的情况。 我还读到相关值和原始值不能共存。也许还有其他方法可以在没有原始值的情况下存档枚举?

希望有人可以帮助我,谢谢

3 个答案:

答案 0 :(得分:2)

您正在处理出现的问题,因为Swift本机功能并不总能与Objective-C一起使用。 NSCoding源于Objective-C世界,而Objective-C对Swift Enum一无所知,因此您不能简单地归档Enum。

通常,您可以使用原始值对枚举进行编码/解码,但正如您所发现的,您无法在Swift枚举中组合关联类型和原始值。

不幸的是,这意味着您需要构建自己的“原始”值方法并在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)
        }
    }
}

然后您可以像这样编码/解码:

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

麒麟(Creature.UnicornColor.pink)

就个人而言,我会使Creature成为一个类并使用继承来处理独角兽和其他类型之间的差异

答案 1 :(得分:1)

您的问题的主要问题是您无法将Swift枚举传递给encode(_:forKey:)

Paulw11所示的

This article将帮助您解决这一问题。如果枚举很容易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,您最好将其重命名为更合适的名称。)

使用上面显示的定义,您可以使用上面链接中显示的代码。

答案 2 :(得分:1)

为了简化编码/解码,您可以为需要Data的Creature和名为data的计算属性提供初始值设定项。随着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")
  }
}

生物进出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
  }
}

enter image description here