ObservableDefaults
是一个 Swift 库,它将 UserDefaults
与 WWDC 2023 中引入的新的 SwiftUI Observation 框架集成在一起。它提供了一个宏 @ObservableDefaults
,通过自动将声明的存储属性与 UserDefaults
键关联,从而简化了 UserDefaults
数据的管理。这使得能够精确而高效地响应 UserDefaults
的变化,无论这些变化来自应用程序内部还是外部。
在 SwiftUI 中管理多个 UserDefaults 键可能会导致代码臃肿并增加出错的风险。虽然 @AppStorage 简化了单个 UserDefaults 键的处理,但它在处理多个键时无法很好地扩展,也无法提供精确的视图更新。随着 Observation 框架的引入,需要一种解决方案能够有效地将 UserDefaults 与 SwiftUI 的状态管理连接起来。
创建 ObservableDefaults 是为了应对这些挑战,提供一个全面而实用的解决方案。它利用宏来减少样板代码,并确保您的 SwiftUI 视图能够准确地响应 UserDefaults 中的更改。
有关 @AppStorage 的局限性以及 ObservableDefaults 背后的动机的深入讨论,您可以阅读 我的博客上的完整文章。
不要错过有关 Swift、SwiftUI、Core Data 和 SwiftData 的最新更新和精彩文章。订阅 Fatbobman's Swift Weekly,每周直接在您的收件箱中接收见解和有价值的内容。
UserDefaults
的自动同步。UserDefaults
键和前缀。您可以使用 Swift Package Manager 将 ObservableDefaults
添加到您的项目中
https://github.com/fatbobman/ObservableDefaults
导入 ObservableDefaults
后,您可以使用 @ObservableDefaults
注释您的类,以自动管理 UserDefaults
同步
import ObservableDefaults
@ObservableDefaults
class Settings {
var name: String = "Fatbobman"
var age: Int = 20
}
这个宏会自动
name
和 age
属性与 UserDefaults
键关联。您可以像这样在您的 SwiftUI 视图中使用 Settings
类
import SwiftUI
struct ContentView: View {
@State var settings = Settings()
var body: some View {
VStack {
Text("Name: \(settings.name)")
TextField("Enter name", text: $settings.name)
}
.padding()
}
}
该库提供了额外的宏以进行更精细的控制
@ObservableOnly
: 该属性是可观察的,但未存储在 UserDefaults
中。@Ignore
: 该属性既不可观察也不存储在 UserDefaults
中。@DefaultsKey
: 指定属性的自定义 UserDefaults
键。@DefaultsBacked
: 该属性存储在 UserDefaults
中并且可观察。@ObservableDefaults
public class Test1 {
@DefaultsKey(userDefaultsKey: "firstName")
// Automatically adds @DefaultsBacked
public var name: String = "fat"
// Automatically adds @DefaultsBacked
public var age = 109
// Only observes, not persisted in UserDefaults
@ObservableOnly
public var height = 190
// Not observable and not persisted
@Ignore
public var weight = 10
}
在这个例子中
name
存储在键为 "fullName"
的 UserDefaults
中。height
是可观察的,但未存储在 UserDefaults
中。weight
既不可观察也不存储在 UserDefaults
中。如果所有属性都有默认值,则可以使用自动生成的初始化器
public init(
userDefaults: UserDefaults? = nil,
ignoreExternalChanges: Bool? = nil,
prefix: String? = nil
)
userDefaults
: 要使用的 UserDefaults
实例(默认为 .standard
)。ignoreExternalChanges
: 如果为 true
,则该实例忽略外部 UserDefaults
更改(默认为 false
)。prefix
: 与此类关联的所有 UserDefaults
键的前缀。前缀不得包含 '.' 字符。@State var settings = Settings(
userDefaults: .standard,
ignoreExternalChanges: false,
prefix: "myApp_"
)
您还可以直接在 @ObservableDefaults
宏中设置参数
userDefaults
: 要使用的 UserDefaults
实例。ignoreExternalChanges
: 是否忽略外部更改。prefix
: UserDefaults
键的前缀。autoInit
: 是否自动生成初始化器(默认为 true
)。observeFirst
: 观察优先级模式。启用时(设置为 true),只有显式标记为 @DefaultsBacked
的属性才会对应于 UserDefaults,而其他属性将被视为 ObservableOnly。默认值为 false@ObservableDefaults(autoInit: false, ignoreExternalChanges: true, prefix: "myApp_")
class Settings {
@DefaultsKey(userDefaultsKey: "fullName")
var name: String = "Fatbobman"
}
如果将 autoInit
设置为 false
,则需要创建自己的初始化器并显式开始监听 UserDefaults
更改
init() {
// Start listening for changes
observerStarter()
}
建议将 UserDefaults
数据与您的主应用程序状态分开管理
@Observable
class ViewState {
var selection = 10
var isLogin = false
let settings = Settings()
}
struct ContentView: View {
@State var state = ViewState()
var body: some View {
VStack(spacing: 30) {
Text("Name: \(state.settings.name)")
Button("Modify Instance Property") {
state.settings.name = "User \(Int.random(in: 0...1000))"
}
Button("Modify UserDefaults Directly") {
UserDefaults.standard.set("User \(Int.random(in: 0...1000))", forKey: "name")
}
}
.buttonStyle(.bordered)
}
}
您可以通过在 @ObservableDefaults
宏中设置 observeFirst 参数来启用此模式
@ObservableDefaults(observeFirst: true)
启用此模式后,只有显式标记为 @DefaultsBacked
的属性才会持久保存到 UserDefaults。所有其他属性将自动应用 @ObservableOnly
宏,使其可观察但不会持久保存。可以将其视为标准模式的逆向模式,专注于可观察性,同时根据需要向单个属性添加持久性功能。
// Observe First Mode
@ObservableDefaults(observeFirst: true)
public class Test2 {
// Automatically adds @ObservabeOnly
public var name: String = "fat"
// Automatically adds @ObservabeOnly
public var age = 109
// In Observe First Mode, only properties that need to be persisted require the use of @DefaultsBacked for annotation, and userDefaultsKey can be set within it
@DefaultsBacked(userDefaultsKey: "myHeight")
public var height = 190
// Not observable and not persisted
@Ignore
public var weight = 10
}
ObservableDefaults
实例响应 UserDefaults
中的外部更改。您可以通过将 ignoreExternalChanges
设置为 true
来禁用此功能。prefix
参数来防止键冲突。@DefaultsKey
为属性指定自定义键。ObservableDefaults
在 MIT 许可证下发布。有关详细信息,请参阅 LICENSE。
特别感谢 Swift 社区的持续支持和贡献。