用户通常期望应用程序存储其数据,以便创建更个性化的体验,因此 iOS 提供了几种读取和写入用户数据的方法。
一种常见的存储少量数据的方式是 UserDefaults,它非常适合存储简单的用户偏好。虽然没有明确的“少量”定义,但你存储在 UserDefaults 中的所有数据都会在应用启动时自动加载——如果你存储了很多数据,可能会导致应用启动变慢。为了给你一个大致的概念,你应该尽量避免在 UserDefaults 中存储超过 512KB 的数据。
提示:如果你在想“512KB到底有多少?”那么给你一个粗略的估计:大约相当于你到目前为止阅读的所有章节内容的文本量。
UserDefaults 非常适合存储一些例如“用户上次启动应用的时间”、“用户最后阅读的新闻故事”或其他被动收集的信息。更好的是,SwiftUI 通常会通过一个名为 @AppStorage 的简单属性包装器将 UserDefaults 封装起来——它目前仅支持一部分功能,但在某些场景下非常有用。
下面是一个有按钮的视图,按钮上显示点击次数,并且每次点击按钮时,点击次数会增加:
struct ContentView: View {
@State private var tapCount = 0
var body: some View {
Button("Tap count: \(tapCount)") {
tapCount += 1
}
}
}
因为这是一个非常重要的应用程序,我们想要保存用户点击的次数,以便当用户下次进入应用时,可以继续他们的操作。
为此,我们需要在按钮的操作闭包中将数据写入 UserDefaults。因此,在 tapCount += 1
这一行之后,我们加入如下代码:
UserDefaults.standard.set(tapCount, forKey: "Tap")
在这行代码中,我们可以看到三个要点:
UserDefaults.standard
。这是附加到应用程序的内置实例,但在更复杂的应用中,你也可以创建自己的实例。例如,如果你想跨多个应用扩展共享默认设置,你可以创建自己的 UserDefaults 实例。set()
方法来接受任何类型的数据——整数、布尔值、字符串等。Tap
。这个名称是区分大小写的,就像普通的 Swift 字符串一样,使用相同的键来读取数据时要确保一致。接下来,我们可以让 tapCount
在每次启动应用时从 UserDefaults 读取数据,而不是初始化为 0:
@State private var tapCount = UserDefaults.standard.integer(forKey: "Tap")
注意,我们使用了相同的键名,这确保它读取的是相同的整数值。
运行应用程序,点击按钮几次,然后返回 Xcode 重新运行应用,你应该能看到点击次数恢复到你上次的状态。
有两个事情你在代码中看不到,但它们仍然非常重要:
如果没有设置“Tap”键会发生什么? 这是在应用第一次运行时的情况,但如你所见,它仍然可以正常工作——如果找不到键,它将返回 0。虽然为 0
这样的默认值很有帮助,但有时也可能会造成困惑。例如,如果是布尔值类型,boolean(forKey:)
如果找不到键时会返回 false
,但这 false
是你自己设置的吗?还是表示没有值?
数据写入的延迟。iOS 在写入持久存储时需要一些时间——它不会立即保存更改,因为你可能会连续进行多次更改。相反,它会等待一段时间,然后一次性写入所有更改。虽然我们不知道具体等待多长时间,但通常几秒钟就足够了。因此,如果你点击按钮后快速重新启动应用,你可能会发现最近的点击次数没有被保存。尽管之前有方法强制立即写入,但现在已经不再有效——即使用户立即开始终止应用程序,默认设置数据也会被立即写入,所以不会丢失任何数据。
SwiftUI 提供了一个 @AppStorage 属性包装器,封装了 UserDefaults,并且在像这种简单的场景中非常有用。使用它可以让我们有效地忽略 UserDefaults,直接使用 @AppStorage 来代替 @State,如下所示:
struct ContentView: View {
@AppStorage("tapCount") private var tapCount = 0
var body: some View {
Button("Tap count: \(tapCount)") {
tapCount += 1
}
}
}
再次强调,这里有三个要点:
body
属性,以便 UI 反映新数据。tapCount
,但它可以是任何字符串——它不必与属性名相匹配。0
,如果 UserDefaults 中没有现有值,就会使用默认值。显然,使用 @AppStorage 比直接使用 UserDefaults 要简洁得多:它只需要一行代码,而不需要重复键名。但至少目前,@AppStorage 不能方便地存储复杂的对象(例如 Swift 结构体)——可能是因为 Apple 想提醒我们,往 UserDefaults 存储大量数据并不是一个好主意!
在向 App Store 提交应用时,Apple 会要求你说明为什么使用 UserDefaults 存储和加载数据。这同样适用于 @AppStorage 属性包装器。无需担心,这只是确保开发者不会在跨应用间进行用户识别。