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

时间:2018-01-20 17:09:00

标签: swift nsuserdefaults codable

跟进我以前的question

我设法使我的枚举符合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]

3 个答案:

答案 0 :(得分:3)

来自UserDefaults参考:

  

默认对象必须是属性列表 - 即(或   对于集合, NSData ,NSString的实例组合,   NSNumber,NSDate,NSArray或NSDictionary。如果你想存储任何   在其他类型的对象中,通常应将其存档以创建   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) {
    ...
}

答案 1 :(得分:0)

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

Article with good explanation

我的解释

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

  1. 应该保存哪些内容?你的枚举案例没有任何价值,没有什么可以保存的。您可能认为它具有关联值,可以保存到存储中。所以第二个问题。

  2. (假设您将其保存到存储中)从存储中获取保存值后,您将如何确定它是什么情况?您可能认为它可以通过关联值的类型来确定,但是如果您有大量具有相同关联值类型的情况会怎么样?所以编译器会这样做,它不知道在这种情况下该怎么做。

  3. 您的问题

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

答案 2 :(得分:0)

快速4 + 枚举,具有正常 情况,以及具有关联 值的案件

  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. 阅读
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)"
    }
}