【问题标题】:How to save an enum conforming to Codable protocol to UserDefaults?如何将符合 Codable 协议的枚举保存到 UserDefaults?
【发布时间】:2018-01-20 17:08:59
【问题描述】:

跟进我之前的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]

【问题讨论】:

  • 上面例子中的encoded是什么?
  • 对不起,它应该在那里。类型问题。
  • @Kobe:请注意我在您的问题中所做的代码更改。无论如何,解码器都会抛出错误。
  • 请注意,当使用 Decodable 协议解析 JSON 时,您可以抛出 DecodingError 而不是创建自己的错误类型。 stackoverflow.com/a/46458771/2303865

标签: swift nsuserdefaults codable


【解决方案1】:

来自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) {
    ...
}

【讨论】:

  • 不要使用 value(forKey:) 在这种情况下,UserDefaults 有特定的方法来加载数据类型值 data(forKey:),因此不需要有条件地从 Any? 转换为 Data,你所需要的只是解开它的价值。 guard let data = UserDefaults.standard.data(forKey: "state") else {
  • @LeoDabus 谢谢。我完全忘记了那些具体的方法,我更新了答案。
  • @LeoDabus 这取决于 OP(可能 UserState 值绑定到 UISwitch 状态)无论如何,这对我的回答无关紧要,并且与原始问题中的评论更相关,恕我直言.
【解决方案2】:

Swift 4+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)"
    }
}

【讨论】:

    【解决方案3】:

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

    Article with good explanation

    我的解释

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

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

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

    您的问题

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-06
      • 2014-07-23
      • 1970-01-01
      • 2018-11-09
      • 2018-07-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多