可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'm trying to make my class conform to NSCoding
, but running into problems because one of its properties is an enum, like this:
enum Direction {
case north
case south
}
If enums were codable I could do it like this:
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")
}
}
but enums aren't codable so encode()
throws an error.
The answers to "How do I encode enum using NSCoder in swift?" suggest encoding the enum's rawValue
and then initializing it from rawValue
on the other end. But in this case, Direction
doesn't have a rawValue
!
It does have a hashValue
, which seems promising. I can encode its hashValue
without a problem, and decode back to an Int
on the other end. But there doesn't seem to be a way to initialize an enum from its hashValue
, so I can't turn it back into a Direction
.
How can I encode and decode a valueless enum?
回答1:
I think adding a raw value to the enum here is the solution with the least code and is the most maintainable. So if you can modify the enum, add a raw value.
Now let's assume you can't modify the enum. You still can do this in a few ways.
The first one, which I think is quite ugly, is to add an extension
of the enum and add a static method like this:
static func direction(from rawValue: String) -> Direction {
switch rawValue {
case: "north": return .north
case: "south": return .south
default: fatalError()
}
}
To convert Direction
to a codeable value, use String(describing:)
to convert the enum to a string. To convert a string back to an enum, just use the method above.
The second one, slightly better, but still not as good as just adding a raw value.
You use a dictionary:
let enumValueDict: [String: Direction] = [
"north": .north, "south": .south
]
To convert Direction
to a codeable value, use String(describing:)
to convert the enum to a string. To convert a string back to an enum, just access the dictionary.
回答2:
New in Swift 4, an enum is encodable. However, it must have a raw value. You can easily do with no additional code, however:
enum Direction : String, Codable {
case north
case south
}
To make your Codable enum work with an NSCoding class MyClass, implement your init(coder:)
and encode(with:)
like this:
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")
}
}
回答3:
You can define some keys and store them instead.
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
It's a bit tricky way. You should always look after the library with Direction
is part of. And add keys for new directions.
回答4:
One option is to make the enum conform to Codable
. This could be through whichever mechanism you want (e.g. use a raw value such as Int or String, or implement the methods).
So lets assume we have the following:
enum Direction { case north, south, east, west }
extension Direction: Codable {
// etc etc - make it conform
}
final class MyClass: NSObject {
var direction: Direction!
}
In the required methods for NSCoding
, you can then do the following:
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
}
}
So, this works, because NSKeyedArchiver
and NSKeyedUnarchiver
are aware of Codable
, but NSCoder
is not. However, given that NSArchiver
and NSUnarchiver
are now deprecated, and essentially, non-keyed archiving is strongly discouraged, so for your project, it is safe to use fatalError()
in this manner - assuming that you test your code.
回答5:
With Swift 5, if your enum Direction
can have raw values, you can use Codable
(Decodable
and Encodable
protocols) as an alternative to NSCoding
:
enum Direction: String, Codable {
case north, south
}
final class MyClass: Codable {
let direction: Direction
init(direction: Direction) {
self.direction = direction
}
}
Usage:
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
*/