【问题标题】:How can I encode (with NSCoding) an enum that has no rawValue?如何编码(使用 NSCoding)没有 rawValue 的枚举?
【发布时间】:2017-04-20 06:46:56
【问题描述】:

我正在尝试使我的类符合NSCoding,但遇到了问题,因为它的属性之一是枚举,如下所示:

enum Direction {
    case north
    case south
}

如果枚举是可编码的,我可以这样做:

class MyClass: NSObject, NSCoding {
    var direction: Direction!
    required init(coder aDecoder: NSCoder) {
        direction = aDecoder.decodeObject(forKey: "direction") as! Direction
    }
    func encode(with aCoder: NSCoder) {
        aCoder.encode(direction, forKey: "direction")
    }
}

但枚举不可编码,因此encode() 会引发错误。

How do I encode enum using NSCoder in swift?”的答案建议对枚举的rawValue 进行编码,然后从另一端的rawValue 对其进行初始化。但在这种情况下,Direction 没有rawValue

它确实有一个hashValue,看起来很有希望。我可以毫无问题地对其hashValue 进行编码,然后在另一端解码回Int。但是似乎没有办法从它的hashValue 初始化一个枚举,所以我不能把它改回Direction

如何编码和解码无价值的枚举?

【问题讨论】:

  • 因此您无法更改 Enum 以添加原始值。但是你可以对 Enum 进行其他更改吗?
  • @Sweeper 我希望有一种方法可以在不更改枚举的情况下做到这一点。不过我很好奇,有什么变化(除了添加 rawValue)会有所帮助?
  • 我在考虑添加一个初始化程序

标签: swift encoding enums swift3 nscoding


【解决方案1】:

Swift 4 中的新功能,枚举 可编码的。但是,它必须具有原始值。但是,您无需额外代码即可轻松完成:

enum Direction : String, Codable {
    case north
    case south
}

要使您的 Codable 枚举与 NSCoding 类 MyClass 一起工作,请像这样实现您的 init(coder:)encode(with:)

class MyClass: NSObject, NSCoding {
    var direction: Direction!
    required init(coder aDecoder: NSCoder) {
        direction = (aDecoder as! NSKeyedUnarchiver).decodeDecodable(Direction.self, forKey: "direction")
    }
    func encode(with aCoder: NSCoder) {
        try? (aCoder as! NSKeyedArchiver).encodeEncodable(direction, forKey: "direction")
    }
}

【讨论】:

    【解决方案2】:

    您可以定义一些键并存储它们。

    fileprivate let kDirectionKeyNorth = 1
    fileprivate let kDirectionKeySouth = 2
    
    // ...
    
    required init(coder aDecoder: NSCoder) {
        let key = aDecoder.decodeInteger(forKey: "direction")
        switch key {
            case kDirectionKeyNorth:
        // ...
        }
    }
    
    // and vise versa
    

    这有点棘手。您应该始终照顾带有Direction 的库。并为新方向添加键。

    【讨论】:

    • 所以,如果我理解这一点,您是在为枚举的每种情况分配一个已知值并对其进行编码,然后从解码值手动创建一个新枚举?
    • @Robert 对!并且由于switch 语句必须是详尽无遗的,如果出现新的Direction 案例,您将在编译时遇到问题。
    【解决方案3】:

    我认为在这里向枚举添加原始值是代码最少且最易于维护的解决方案。所以如果你可以修改枚举,添加一个原始值。

    现在让我们假设您不能修改枚举。您仍然可以通过几种方式做到这一点。

    第一个,我觉得挺难看的,就是给枚举加一个extension,加一个静态方法是这样的:

    static func direction(from rawValue: String) -> Direction {
        switch rawValue {
            case: "north": return .north
            case: "south": return .south
            default: fatalError()
        }
    }
    

    要将Direction 转换为可编码值,请使用String(describing:) 将枚举转换为字符串。要将字符串转换回枚举,只需使用上述方法即可。

    第二个,稍微好一点,但仍然不如只添加一个原始值。

    你使用字典:

    let enumValueDict: [String: Direction] = [
        "north": .north, "south": .south
    ]
    

    要将Direction 转换为可编码值,请使用String(describing:) 将枚举转换为字符串。要将字符串转换回枚举,只需访问字典即可。

    【讨论】:

    • @arionik 哎呀!已更正
    【解决方案4】:

    一种选择是使枚举符合Codable。这可以通过您想要的任何机制(例如,使用 Int 或 String 等原始值,或实现方法)。

    假设我们有以下内容:

    enum Direction { case north, south, east, west }
    
    extension Direction: Codable {
        // etc etc - make it conform
    }
    
    final class MyClass: NSObject {
        var direction: Direction!
    }
    

    NSCoding 所需的方法中,您可以执行以下操作:

    extension MyClass: NSCoding {
    
        func encode(with aCoder: NSCoder) {
            guard let keyedCoder = aCoder as? NSKeyedArchiver else {
                fatalError("Must use Keyed Coding")
            }
            try! keyedCoder.encodeEncodable(direction, forKey: "direction")
        }
    
        convenience init?(coder aDecoder: NSCoder) {
            self.init()
            guard let keyedDecoder = aDecoder as? NSKeyedUnarchiver else { 
                fatalError("Must use Keyed Coding") 
            }
            direction = keyedDecoder.decodeDecodable(Direction.self, forKey: "direction") ?? .north
        }
    }
    

    所以,这行得通,因为NSKeyedArchiverNSKeyedUnarchiver 知道Codable,但NSCoder 不知道。但是,鉴于 NSArchiverNSUnarchiver 现在已弃用,并且基本上,非密钥归档 强烈反对,因此对于您的项目,以这种方式使用 fatalError() 是安全的 -假设你测试你的代码。

    【讨论】:

      【解决方案5】:

      在 Swift 5 中,如果您的枚举 Direction 可以有原始值,您可以使用 CodableDecodableEncodable 协议)作为 NSCoding 的替代方案:

      enum Direction: String, Codable {
          case north, south
      }
      
      final class MyClass: Codable {
      
          let direction: Direction
      
          init(direction: Direction) {
              self.direction = direction
          }
      
      }
      

      用法:

      import Foundation
      
      let encoder = JSONEncoder()
      let myClass = MyClass(direction: .north)
      
      if let data = try? encoder.encode(myClass),
          let jsonString = String(data: data, encoding: .utf8) {
          print(jsonString)
      }
      
      /*
       prints:
       {"direction":"north"}
       */
      
      import Foundation
      
      let jsonString = """
      {
        "direction" : "south"
      }
      """
      
      let jsonData = jsonString.data(using: .utf8)!
      let decoder = JSONDecoder()
      let myClassInstance = try! decoder.decode(MyClass.self, from: jsonData)
      dump(myClassInstance)
      
      /*
       prints:
       ▿ __lldb_expr_319.MyClass #0
         - direction: __lldb_expr_319.Direction.south
       */
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-01-15
        • 1970-01-01
        • 1970-01-01
        • 2016-04-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多