代码之家  ›  专栏  ›  技术社区  ›  Adrian

如何将符合可编码协议的枚举保存到UserDefaults?

  •  3
  • Adrian  · 技术社区  · 8 年前

    我之前的跟进 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 回复  |  直到 8 年前
        1
  •  3
  •   djromero    8 年前

    从…起 UserDefaults 参考号:

    默认对象必须是属性列表,即(或 对于集合,是的实例组合) 操作文件数据 ,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) {
        ...
    }
    
        2
  •  2
  •   Tung Fam    7 年前

    Swift 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. 用户默认值:
    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
  •  0
  •   LowKostKustomz    8 年前

    枚举应具有rawValue才能保存为可编码对象。您的枚举事例具有关联的值,因此它们不能具有rawValue。

    Article with good explanation

    我的解释

    假设您可以保存对象 .LoggedIn(.playing) 到用户默认值。您要保存 UserState .LoggedIn 案例有两个主要问题:

    1. 应将哪些内容保存到存储中?您的枚举大小写没有任何值,没有要保存的内容。您可能认为它具有关联值,可以将其保存到存储器中。第二个问题。

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

    你的问题

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