如何将符合 Codable 协议的枚举保存到 UserDefaults?

How to save an enum conforming to Codable protocol to UserDefaults?

跟进我之前的 :

我设法使我的枚举符合 Codable 协议,实现了 init() 和 encode() 并且它似乎工作正常。

enum UserState {
    case LoggedIn(LoggedInState)
    case LoggedOut(LoggedOutState)
}

enum LoggedInState: String {
    case playing
    case paused
    case stopped
}

enum LoggedOutState: String {
    case Unregistered
    case Registered
}

extension UserState: Codable {
    enum CodingKeys: String, CodingKey {
        case loggedIn
        case loggedOut
    }

    enum CodingError: Error {
        case decoding(String)

    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        if let loggedIn = try? values.decode(String.self, forKey: .loggedIn) {
            self = .LoggedIn(LoggedInState(rawValue: loggedIn)!)
        }
        else if let loggedOut = try? values.decode(String.self, forKey: .loggedOut) {
            self = .LoggedOut(LoggedOutState(rawValue: loggedOut)!)
        }
        else {
            throw CodingError.decoding("Decoding failed")
        }
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)

        switch self {
        case let .LoggedIn(value):
            try container.encode(value.rawValue, forKey: .loggedIn)
        case let .LoggedOut(value):
            try container.encode(value.rawValue, forKey: .loggedOut)
        }
    }
}



class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let state = UserState.LoggedIn(.playing)
        UserDefaults.standard.set(state, forKey: "state")
    }
}

我的问题是我不知道如何将它保存到 UserDefaults。如果我像现在一样保存它,我会在 运行 应用程序时收到以下错误:

[User Defaults] Attempt to set a non-property-list object Codable.UserState.LoggedIn(Codable.LoggedInState.playing) as an NSUserDefaults/CFPreferences value for key state
2018-01-20 19:06:26.909349+0200 Codable[6291:789687]

Enum 应该有 rawValue 才能保存为 Codable 对象。您的枚举案例具有关联值,因此它们不能具有 rawValue。

Article with good explanation

我的解释

假设您可以将对象 .LoggedIn(.playing) 保存到 UserDefaults。您想要保存 UserState.LoggedIn 案例。主要有2个问题:

  1. 存储应该保存什么?您的枚举案例没有任何价值,没有什么可保存的。你可能认为它有关联的价值,它可以保存到存储器中。那么第2题。

  2. (假设你把它保存到存储中)当你从存储中获取保存的值后,你将如何确定它是什么情况?您可能认为它可以由关联值的类型来确定,但是如果您有很多具有相同关联值类型的案例怎么办?所以编译器不知道在这种情况下该怎么做。

你的问题

您正在尝试保存枚举的大小写本身,但默认情况下保存到 UserDefaults 不会转换您的对象。您可以尝试将 .LoggedIn 案例编码为 Data,然后将结果数据保存到 UserDefaults。当您需要获取保存的值时,您将从存储中获取数据并从数据中解码枚举的大小写。

来自UserDefaults参考:

A default object must be a property list—that is, an instance of (or for collections, a combination of instances of) NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. If you want to store any other type of object, you should typically archive it to create an instance of NSData.

因此您应该手动编码 state 并将其存储为数据:

if let encoded = try? JSONEncoder().encode(state) {
    UserDefaults.standard.set(encoded, forKey: "state")
}

然后,回读:

guard let data = UserDefaults.standard.data(forKey: "state") else { fatalError() }
if let saved = try? JSONDecoder().decode(UserState.self, from: data) {
    ...
}

Swift4+Enum 具有 normal case,以及 case 关联 价值.

  1. 枚举的代码:
enum Enumeration: Codable {
    case one
    case two(Int)

    private enum CodingKeys: String, CodingKey {
        case one
        case two
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        if let value = try? container.decode(String.self, forKey: .one), value == "one" {
            self = .one
            return
        }

        if let value = try? container.decode(Int.self, forKey: .two) {
            self = .two(value)
            return
        }

        throw _DecodingError.couldNotDecode
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)

        switch self {
        case .one:
            try container.encode("one", forKey: .one)
        case .two(let number):
            try container.encode(number, forKey: .two)
        }
    }
}
  1. 写入UserDefaults:
let one1 = Enumeration.two(442)

if let encoded = try? encoder.encode(one1) {
    UserDefaults.standard.set(encoded, forKey: "savedEnumValue")
}
  1. 从 UserDefaults 读取:
if let savedData = UserDefaults.standard.object(forKey: "savedEnumValue") as? Data {
    if let loadedValue = try? JSONDecoder().decode(Enumeration.self, from: savedData) {
        print(loadedValue) // prints: "two(442)"
    }
}