阅读 181

[SwiftUI 100 天] 用 UserDefaults 保存和加载数据

译自 www.hackingwithswift.com/books/ios-s…

更多内容,欢迎关注公众号 「Swift花园」

喜欢文章?不如来个 🔺💛➕三连?关注专栏,关注我 🚀🚀🚀

用 UserDefaults 保存和加载数据

应用的大部分已经可以工作,但还有一个致命缺陷:应用重启后所有数据都被擦除,这显然不合理。我们可以让 Prospects 构造器从 UserDefaults 中加载数据,在数据发生变化时再写回 UserDefaults

这回我们的数据存储会用一种稍微简单一点的格式:虽然 Prospects 类用了 @Published 属性包装器,它的 people 数组已经遵循了 Codable 协议。所以我们只需要再做三个小改动,就可以实现目标:

  1. 更新 Prospects 构造器,让它能从 UserDefaults 中加载数据。
  2. 添加一个 save() 方法,用于把当前数据写入 UserDefaults
  3. 在添加某个 prospect 或者触发它的 isContacted 属性时调用 save()

构造器代码如下:

init() {
    if let data = UserDefaults.standard.data(forKey: "SavedData") {
        if let decoded = try? JSONDecoder().decode([Prospect].self, from: data) {
            self.people = decoded
            return
        }
    }

    self.people = []
}
复制代码

至于 save() 方法,要做的事情相反,添加下面的代码:

func save() {
    if let encoded = try? JSONEncoder().encode(people) {
        UserDefaults.standard.set(encoded, forKey: "SavedData")
    }
}
复制代码

我们的数据会在两种情况下发生变化,需要在两种情况下调用 save() 来确保数据被写入。

第一种情况是 Prospectstoggle() 方法,修改成下面:

func toggle(_ prospect: Prospect) {
    objectWillChange.send()
    prospect.isContacted.toggle()
    save()
}
复制代码

第二种情况是 ProspectsViewhandleScan(result:) 方法,我们在这里添加新的 prospect 到数组,找到这一行:

self.prospects.people.append(person)
复制代码

在下面添加这行代码:

self.prospects.save()
复制代码

运行代码,你会发现即使重启应用,所有的联系人都还在。不过,我还要更进一步,再修复两个其他的问题:

  1. 我们硬编码了两处 “SavedData”,这可能在将来因为名字变化引发问题。
  2. ProspectsView 中调用 save 不是一种好的设计,因为视图本不该知道模型内部的工作,也因为我们还要和别的视图协同,有可能在那些地方忘记调用 save()

为了修复第一个问题,我们应该创建一个包含保存键的静态属性。

把下面这行代码添加到 Prospects 类:

static let saveKey = "SavedData"
复制代码

修改构造器,避免硬编码:

if let data = UserDefaults.standard.data(forKey: Self.saveKey) {
复制代码

然后修改 save() 方法:

UserDefaults.standard.set(encoded, forKey: Self.saveKey)
复制代码

长期来看,这个方法要安全地多 —— 因为我们很可能意外地把键的字符串写成 “SaveKey” 或者 “savedKey”,由此引入各种 bug。

至于 save(),实际上是一个更深层次的问题:当我们编写像 self.prospects.people.append(person) 这样的代码时,我们实际上打破了一项众所周知的软件工程准则:封装。这其中包含的理念是,我们应当对外部对象能对某个类或者结构体执行的读写访问进行限制。

具体来讲,我们不应该写 self.prospects.people.append(person) 而应该在 Prospects 类中添加 add() 方法,然后写 self.prospects.add(person)。这样做的结果当然一样 —— 添加一个人到 people 数组 —— 但实现被隐藏了。

代码如下:

func add(_ prospect: Prospect) {
    people.append(prospect)
    save()
}
复制代码

更好的是,我们可以利用访问控制来防止外部对 people 数组的写入,也就是说,视图只能通过 add() 方法来添加 prospect,把 people 属性的定义修改如下:

@Published private(set) var people: [Prospect]
复制代码

现在,只有 Prospects 内部的代码可以调用 save() 方法,所以我们也可以把它也标记为 private:

private func save() {
复制代码

这样一来,我们的代码就不会因为意外被错误修改 —— 编译器不会允许这种操作。

把原来的代码替换为:

self.prospects.add(person)
复制代码

替换字符串硬编码,使用封装和访问控制,都是让代码变得更安全的简单方法,但却是构建更好软件的重要步骤。


我的公众号 这里有Swift及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~

Swift花园微信公众号