首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >问答首页 >向已保存的用户默认变量中添加变量

向已保存的用户默认变量中添加变量
EN

Stack Overflow用户
提问于 2020-11-09 07:21:37
回答 3查看 375关注 0票数 2

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

代码语言:javascript
代码运行次数:0
运行
复制
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结构:

代码语言:javascript
代码运行次数:0
运行
复制
struct Person: Encodable, Decodable {
    var firstName: String
    var lastName: String
    var birthday: Date

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

我还有保存/加载Person结构的代码(即使用上面的代码)。

储蓄:

代码语言:javascript
代码运行次数:0
运行
复制
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)")
    }

加载:

代码语言:javascript
代码运行次数:0
运行
复制
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

代码语言:javascript
代码运行次数:0
运行
复制
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()
    }
}

在更新之前,应用程序(在Person中没有favorite变量)将在没有favorite变量的情况下保存。在更新之后,应用程序将尝试用Person变量加载先前保存的favorite。这就是它失败的地方,因为旧版本的数据中没有favorite变量。所以它会抛出一个错误。

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

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

提前谢谢。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2020-11-09 07:34:08

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

代码语言:javascript
代码运行次数:0
运行
复制
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声明为可选的:

代码语言:javascript
代码运行次数:0
运行
复制
var favorite: Bool?

如果数据中不存在favorite,而不是您想要的false,这将导致它被设置为nil。如果您真的想要false,可以使用隐式未包装的可选Bool?,每次解码时都需要将nil更改为false

代码语言:javascript
代码运行次数:0
运行
复制
self.person = try UserDefaults.standard.getToObject(forKey: "person", castTo: Person.self)
if self.person.favorite == nil {
    self.person.favorite = false
}

如果您担心会忘记这样做,则可以使getToObject只接受符合此协议的对象:

代码语言:javascript
代码运行次数:0
运行
复制
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

代码语言:javascript
代码运行次数:0
运行
复制
extension Person : HasDefaults {
    func changeNilsToDefaults() {
        if self.person.favorite == nil {
            self.person.favorite = false
        }
    }
}
票数 3
EN

Stack Overflow用户

发布于 2020-11-09 07:56:54

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

DTO:数据传输对象

将对象保存为DTO对象,并在第一次发布后将新属性添加为可选属性:

代码语言:javascript
代码运行次数:0
运行
复制
struct PersonDTO: Codable {
    let firstName: String
    let lastName: String
    let birthday: Date
    let favorite: Bool?
}

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

代码语言:javascript
代码运行次数:0
运行
复制
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
    }
}
票数 1
EN

Stack Overflow用户

发布于 2021-02-17 13:00:55

创建一个protected变量:

代码语言:javascript
代码运行次数:0
运行
复制
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很大,这可能需要很长时间。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/64747293

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档