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

Posted

技术标签:

【中文标题】如何编码(使用 NSCoding)没有 rawValue 的枚举?【英文标题】:How can I encode (with NSCoding) an enum that has no 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)会有所帮助? 我在考虑添加一个初始化程序 【参考方案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
 */

【讨论】:

以上是关于如何编码(使用 NSCoding)没有 rawValue 的枚举?的主要内容,如果未能解决你的问题,请参考以下文章

Swift 和 NSCoding:对符合类协议的对象数组进行编码

如何使用 NSCoding 保存结构

编码对象

iOS-NSCoder 和 NSCoding

如何使用 NSKeyedArchiver 对自定义类进行编码和解码

如何归档 NSDictionary 的 NSArray(使用 NSCoding)?