如何存档具有关联值的枚举?

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