我如何编码(使用 NSCoding)没有 rawValue 的枚举?

How can I encode (with NSCoding) an enum that has no rawValue?

我试图让我的 class 符合 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.

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

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

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 属于的库。并为新方向添加键。

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

现在假设您无法修改枚举。您仍然可以通过几种方式执行此操作。

第一个,我认为是相当丑陋的,是添加一个 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:) 将枚举转换为字符串。要将字符串转换回枚举,只需访问字典即可。

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

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

要使您的 Codable 枚举与 NSCoding class 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")
    }
}

使用 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
 */

一种选择是使枚举符合 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() 以这种方式 - 假设您测试您的代码。