如何编码(使用 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
所以,这行得通,因为NSKeyedArchiver
和NSKeyedUnarchiver
知道Codable
,但NSCoder
不知道。但是,鉴于 NSArchiver
和 NSUnarchiver
现在已弃用,并且基本上,非密钥归档 强烈反对,因此对于您的项目,以这种方式使用 fatalError()
是安全的 -假设你测试你的代码。
【讨论】:
【参考方案5】:在 Swift 5 中,如果您的枚举 Direction
可以有原始值,您可以使用 Codable
(Decodable
和 Encodable
协议)作为 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:对符合类协议的对象数组进行编码