将变量添加到已保存的用户默认值变量 创建一个 protected 变量:

问题描述

我下载了一个快速文件,应该可以帮助我保存和加载自定义变量:

import Foundation

protocol ObjectSavable {
func setToObject<Object>(_ object: Object,forKey: String) throws where Object: encodable
func getToObject<Object>(forKey: String,castTo type: Object.Type) throws -> Object where Object: Decodable
}

extension UserDefaults: ObjectSavable {
func setToObject<Object>(_ object: Object,forKey: String) throws where Object: encodable {
    let encoder = JSONEncoder()
    do {
        let data = try encoder.encode(object)
        set(data,forKey: forKey)
    } catch {
        throw ObjectSavableError.unabletoEncode
    }
}

func getToObject<Object>(forKey: String,castTo type: Object.Type) throws -> Object where Object: Decodable {
    guard let data = data(forKey: forKey) else { throw ObjectSavableError.novalue }
    let decoder = JSONDecoder()
    do {
        let object = try decoder.decode(type,from: data)
        return object
    } catch {
        throw ObjectSavableError.unabletoDecode
    }
}
}

enum ObjectSavableError: String,LocalizedError {
case unabletoEncode = "Unable to encode object into data"
case novalue = "No data object found for the given key"
case unabletoDecode = "Unable to decode object into given type"
}

我有这个Person结构:

struct Person: encodable,Decodable {
    var firstName: String
    var lastName: String
    var birthday: Date

    init() {
        self.firstName = "Tim"
        self.lastName = "Cook"
        self.birthday = Date()
    }
}

我也有这段代码用于保存/加载Person结构(正在使用上面的代码

保存:

print("Saving object...")
    let person: Person = Person()
    
    do {
        try UserDefaults.standard.setToObject(person,forKey: "person")
        print("Object saved successfully")
    } catch let err {
        print("Error while saving object:\n\(err.localizedDescription)")
    }

正在加载:

print("Loading object...")
    
    do {
        self.person = try UserDefaults.standard.getToObject(forKey: "person",castTo: Person.self)
        print("Successfully load object:\n\(self.person!)")
    } catch let err {
        print("Error while loading object:\n\(err.localizedDescription)")
    }

现在,所有这些都有效。 但是,假设我以这种方式发布了我的应用,然后我想向Person添加一个新变量,例如,我将添加一个favorite

struct Person: encodable,Decodable {
    var firstName: String
    var lastName: String
    var birthday: Date
    var favorite: Bool = false

    init() {
        self.firstName = "Tim"
        self.lastName = "Cook"
        self.birthday = Date()
    }
}

在更新之前,应用程序(在favorite中没有Person变量)将被保存,而没有favorite变量。更新之后,应用程序将尝试使用Person变量加载先前保存的favorite。这就是失败的原因,因为较旧版本的数据中没有favorite变量。因此会引发错误

我的问题是,有没有办法在从用户认值中解码Person时,如果找不到任何匹配的变量(例如:favorite),而不是抛出错误,它将尝试自动创建吗? (来自var favorite = false )?

我的项目:https://github.com/orihpt/Encodable

谢谢。

解决方法

一种方法是将自定义解码代码添加到Person中:

enum CodingKeys : CodingKey {
    case firstName
    case lastName
    case birthday
    case favorite
}

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    firstName = try container.decode(String.self,forKey: .firstName)
    lastName = try container.decode(String.self,forKey: .lastName)
    birthday = try container.decode(Date.self,forKey: .birthday)
    favorite = try container.decodeIfPresent(Bool.self,forKey: .favorite) ?? false
}

请注意,对于favorite,我使用了decodeIfPresent,默认值为false


另一种方法是将favorite声明为可选:

var favorite: Bool?

如果数据中不存在favorite,而不是您想要的nil中没有false,则会将其设置为false。如果您确实想要Bool?,则可以使用一个隐式解包的可选nil,并且每次解码时都需要将false更改为self.person = try UserDefaults.standard.getToObject(forKey: "person",castTo: Person.self) if self.person.favorite == nil { self.person.favorite = false }

getToObject

如果您担心自己可能会忘记这样做,可以使protocol HasDefaults { func changeNilsToDefaults() } extension UserDefaults { func getToObject<Object: HasDefaults>(forKey: String,castTo type: Object.Type) throws -> Object where Object: Decodable { guard let data = data(forKey: forKey) else { throw ObjectSavableError.noValue } let decoder = JSONDecoder() do { let object = try decoder.decode(type,from: data) object.changeNilsToDefaults() // notice this line! return object } catch { throw ObjectSavableError.unableToDecode } } } 仅接受符合此协议的对象:

getToObject(forKey: "person",castTo: Person.self)

除非您使Person符合HasDefaults,否则您将无法执行extension Person : HasDefaults { func changeNilsToDefaults() { if self.person.favorite == nil { self.person.favorite = false } } }

{{1}}
,

@Sweeper的建议是可行的解决方案。您还可以将其“可选”方法与DTO解决方案合并。

DTO: Data transfer object

将对象另存为DTO对象,并在首次发行后将新属性添加为“可选”:

struct PersonDTO: Codable {
    let firstName: String
    let lastName: String
    let birthday: Date
    let favorite: Bool?
}

从UserDefaults获取DTO对象后,使用它初始化您的Person对象。

struct Person {
    var firstName: String
    var lastName: String
    var birthday: Date
    var favorite: Bool

    init(_ dto: PersonDTO) {
        self.firstName = dto.firstName
        self.lastName = dto.lastName
        self.birthday = dto.birthday
        self.favorite = dto.favorite ?? false
    }
}
,

创建一个 protected 变量:

struct Person: Encodable,Decodable {
    var firstName: String
    var lastName: String
    var birthday: Date
    var favorite: Bool {
        get {
            return favoriteProtected ?? false
        }
        set {
            favoriteProtected = newValue
        }
    }

    private var favoriteProtected: Bool? = nil

    init() {
        self.firstName = "Tim"
        self.lastName = "Cook"
        self.birthday = Date()
    }
}

这样您就不必实施 init(from decoder: Decoder),如果您的 struct 很大,这可能需要很长时间。