为 UserDefaults 和 NSUbiquitousKeyValueStore 提供完美接口。
PersistentKeyValueKit 为 UserDefaults 和 NSUbiquitousKeyValueStore 提供了全面、类型安全和通用的接口。它可以轻松地在整个代码库中持久化和检索任何类型的数据。该框架鼓励:
PersistentKeyValueKit 包含了大量的意见、概念和类型,但其实现非常轻量级,并且试图直接位于熟悉的存储 API 之上。
UserDefaults 和 NSUbiquitousKeyValueStore 的所有约束都适用。建议您熟悉这些存储系统。数据不会在存储之间自动迁移。
PersistentKeyValueKit 由强大的测试套件支持。
KeyValuePersistible 协议的类型。UserDefaults 和 NSUbiquitousKeyValueStore 的通用接口。NSUbiquitousKeyValueStore 需要 watchOS 9.0+。dependencies: [
.package(url: "https://github.com/kylehughes/PersistentKeyValueKit.git", .upToNextMajor(from: "1.0.0")),
]
通过符合 KeyValuePersistible 协议使类型可持久化。
import PersistentKeyValueKit
enum RuntimeColorScheme: String {
case dark
case light
case system
}
extension RuntimeColorScheme: KeyValuePersistible {
static var persistentKeyValueRepresentation: some PersistentKeyValueRepresentation<Self> {
RawRepresentablePersistentKeyValueRepresentation()
}
}
定义一个键,其值为该类型。
import PersistentKeyValueKit
extension PersistentKeyProtocol where Self == PersistentKey<RuntimeColorScheme> {
static var runtimeColorScheme: Self {
Self("RuntimeColorScheme", defaultValue: .system)
}
}
在 SwiftUI 视图中使用该值……
import PersistentKeyValueKit
struct SettingsView: View {
@PersistentValue(.runtimeColorScheme)
var runtimeColorScheme
}
……或任何其他地方。
userDefaults.get(.runtimeColorScheme)
userDefaults.set(.runtimeColorScheme, to: .dark)
PersistentKey<Value> 将唯一标识符映射到在应用程序启动之间持久存在的强类型 Value。
建议使用静态访问器模式来定义和访问键。 此模式允许您在常用位置定义键,并在任何位置以类型安全的方式访问它们。 这些 API 旨在尽可能符合人体工程学地支持此模式。
例如
extension PersistentKeyProtocol where Self == PersistentKey<Date> {
static var mostRecentLaunchDate: Self {
Self("MostRecentLaunchDate", defaultValue: .distantPast)
}
}
动态识别的键也可以使用静态访问器定义。
例如
extension PersistentKeyProtocol where Self == PersistentKey<String?> {
static func selectedLayoutID(forListID listID: String) -> Self {
Self("\(listID)::SelectedLayoutID", defaultValue: nil)
}
}
键值对可以本地存储在 UserDefaults 中(例如 UserDefaults.standard,UserDefaults(suiteName:))或存储在 iCloud 的 NSUbiquitousKeyValueStore.default 中。
例如
userDefaults.set(.mostRecentLaunchDate, to: .now)
if let layoutID = NSUbiquitousKeyValueStore.default.get(.selectedLayoutID(forListID: listID)) {
调试键是一种键,其值可以在 Debug 构建中修改,但不能在 Release 构建中修改。这使您可以将键用于开发和测试目的,而不必担心它们在生产环境中被修改,同时最大限度地减少需要编写的条件代码量。
所有基于键的接口都接受 PersistentKeyProtocol,PersistentKey 和 PersistentDebugKey 都符合该协议。
警告
调试键只有在从源代码编译此框架时(例如,作为 SwiftPM 依赖项)才能工作。如果使用预构建的二进制文件,则可能不包含 DEBUG 代码路径,并且将始终使用默认值。
例如
extension PersistentKeyProtocol where Self == PersistentDebugKey<Bool> {
static var isAppStoreRatingEnabled: Self {
Self(
"IsAppStoreRatingEnabled",
debugDefaultValue: false,
releaseDefaultValue: true
)
}
}
userDefaults.set(.isAppStoreRatingEnabled, to: false)
userDefaults.get(.isAppStoreRatingEnabled) // false in Debug, true in Release
通过符合 KeyValuePersistible 协议使类型可持久化。
KeyValuePersistible 有一个要求
static var persistentKeyValueRepresentation: some PersistentKeyValueRepresentation<Self> { get }
表示形式是一种描述如何持久化值的类型:它如何存储在 UserDefaults 或 NSUbiquitousKeyValueStore 中,以及如何检索它。
提供了许多常见的表示形式,并且很容易在 KeyValuePersistible 实现内部构建自定义表示形式。存储的原始类型以原生方式表示,因此您的责任是将您的类型转换为原始类型。
例如
extension UIContentSizeCategory: KeyValuePersistible {
static var persistentKeyValueRepresentation: some PersistentKeyValueRepresentation<Self> {
RawRepresentablePersistentKeyValueRepresentation()
}
}
UserDefaults 和/或 NSUbiquitousKeyValueStore 本身支持原始类型。这些类型都是 KeyValuePersistible 并且直接存储,几乎不需要转换。所有其他的 KeyValuePersistible 类型都必须通过 PersistentKeyValueRepresentation 转换为原始类型。
原始类型包括:
Array<Element>,其中 Element: KeyValuePersistibleBoolDataDictionary<String, Value>,其中 Value: KeyValuePersistibleDoubleFloatIntOptional<Wrapped>,其中 Wrapped: KeyValuePersistibleStringURL有一些内置的表示形式,涵盖了应用程序最常见的用例。 它们都在这里描述。 如果需要,可以使用它们的构建块,但此处不描述。
ProxyPersistentKeyValueRepresentation 是一种使用 Proxy 的表示形式作为自身的表示形式。 Proxy 类型必须是 KeyValuePersistible 类型。
使用此表示形式来依赖于适合该类型的现有表示形式。
这是类型在其之上构建的基本表示形式。 一种常见的模式是使用原始类型作为代理类型,但是任何 KeyValuePersistible 类型都可以用作代理类型。 间接层数没有限制。
例如
Date 持久化为 TimeInterval(即 Double)。
extension Date: KeyValuePersistible {
public static var persistentKeyValueRepresentation: some PersistentKeyValueRepresentation<Self> {
ProxyPersistentKeyValueRepresentation(
to: \.timeIntervalSinceReferenceDate,
from: Date.init(timeIntervalSinceReferenceDate:)
)
}
}
RawRepresentablePersistentKeyValueRepresentation 是一种代理表示形式,如果 RawValue 是 KeyValuePersistible,则将 RawRepresentable 值持久化为 RawValue。
例如
NotificationFrequency 持久化为 String。
enum NotificationFrequency: String {
case daily
case weekly
case monthly
}
extension NotificationFrequency: KeyValuePersistible {
static var persistentKeyValueRepresentation: some PersistentKeyValueRepresentation<Self> {
RawRepresentablePersistentKeyValueRepresentation()
}
}
CodablePersistentKeyValueRepresentation 是一种代理表示形式,它将值持久化为给定编码器和解码器的 Input/Output 类型。
例如
Contact 持久化为 Data。
struct Contact: Codable, Sendable {
let nickname: String
let dateOfBirth: Date
}
@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *)
extension Contact: KeyValuePersistible {
static var persistentKeyValueRepresentation: some PersistentKeyValueRepresentation<Self> {
CodablePersistentKeyValueRepresentation()
}
}
为此默认 JSONDecoder 和 JSONEncoder 提供了此便捷初始化程序。 可以在其他初始化程序中提供 Decoder 和 Encoder。
LosslessStringConvertiblePersistentKeyValueRepresentation 是一种代理表示形式,它将值持久化为 String,如其 LosslessStringConvertible 一致性所定义。
例如
Character 持久化为 String。
extension Character: KeyValuePersistible {
static var persistentKeyValueRepresentation: some PersistentKeyValueRepresentation<Self> {
LosslessStringConvertiblePersistentKeyValueRepresentation()
}
}
每个 KeyValuePersistible 类型都有一个关联的 PersistentKeyValueRepresentation 类型和 persistentKeyValueRepresentation 属性。 这是用于使用存储持久化值的默认表示形式。
但是,可以在定义时为单个键提供表示形式,即使该值不是 KeyValuePersistible。 这对于以下情况很有用:
例如
Date? 使用 ISO 8601 格式持久化为 String。
extension PersistentKeyProtocol where Self == PersistentKey<Date?> {
static var mostRecentAppStoreReviewRequestDate: Self {
Self(
"MostRecentAppStoreReviewRequestDate",
defaultValue: nil,
representation: ProxyPersistentKeyValueRepresentation(
to: { date in date.ISO8601Format() },
from: { string in try? Date.ISO8601FormatStyle().parse(string) }
)
)
}
}
PersistentValue 是一个属性包装器,它提供了一种类型安全的方式来访问和修改 SwiftUI 视图中 UserDefaults 或 NSUbiquitousKeyValueStore 中的值。 它支持自动观察和更新,无论何时给定存储中的值发生更改(本地或其他方式)。
默认存储是来自环境的 defaultPersistentKeyValueStore。 如果未设置,则默认存储为 UserDefaults.standard。
例如
@PersistentValue(.isAppStoreRatingEnabled)
var isAppStoreRatingEnabled: Bool
@PersistentValue(.isAppStoreRatingEnabled, store: .ubiquitous)
var isAppStoreRatingEnabled: Bool
提供了一个视图修饰符,用于设置视图(或其后代)中任何 @PersistentValue 属性包装器使用的默认存储。 可以通过直接在 @PersistentValue 声明中提供默认存储来覆盖默认存储。
例如
extension App: SwiftUI.App {
var body: some Scene {
RootView()
.defaultPersistentKeyValueStore(.ubiquitous)
}
}
PersistentKeyValueKit 支持传统的 UserDefaults 注册。 键的默认值将注册为 UserDefaults 实例注册域中的默认值。
例如
1 将为 UserDefaults 注册域中的默认值字典中的键 LaunchCount 注册。
extension PersistentKeyProtocol where Self == PersistentKey<Int> {
static var launchCount: Self {
Self("LaunchCount", defaultValue: 1)
}
}
userDefaults.register(.launchCount)
注意
仅使用 PersistentKeyValueKit 时,注册不是必需的,因为默认值通过键定义来处理。 当与其他不使用 PersistentKeyValueKit 的框架共享 UserDefaults 时,它变得有用,确保使用原始 UserDefaults API 的代码可以使用默认值。
PersistentKeyValueKit 致力于成为平台存储 API 之上的类型安全基础设施。 只有当它具有压倒性的惯用性、现代性或必要性时,才会更改行为。
平台存储 API 使用隐式默认值。 例如,UserDefaults 将为未设置(或已删除)的键的 Bool 值返回 false,或者为 Int 返回 0。 无法将这些隐式值与未设置的键区分开来; 无法表示 nil 值或值的缺失。
PersistentKeyValueKit 要求每个键都有一个显式默认值。 这是将为未设置的键或具有错误类型值的键返回的值。 每个键都可以不同。 这提供了对值的精细控制以及类型安全的可选性。 如果该键需要表示 nil 值或值的缺失,则可以使用 Optional 类型定义该键,并且其默认值可以为 nil。
例如
此键的值可以为 nil,如果未设置任何值,则将返回 nil。 调用者可以将未设置的键与 true 或 false 值区分开来。
extension PersistentKeyProtocol where Self == PersistentKey<Bool?> {
static var arePushNotificationsEnabled: Self {
Self("ArePushNotificationsEnabled", defaultValue: nil)
}
}
此键的值不能为 nil,如果未设置任何值,则将返回 true。 调用者无法将未设置的键与 true 值区分开来。
extension PersistentKeyProtocol where Self == PersistentKey<Bool> {
static var arePushNotificationsEnabled: Self {
Self("ArePushNotificationsEnabled", defaultValue: true)
}
}
注意
对于 boolean 值缺失的含义的绝对清晰,具有三种情况的枚举通常比 Optional<Bool> 更好。
平台存储 API 支持异构数组 (Array<Any>) 和字典 (Dictionary<String, Any>)。
出于人体工程学考虑,PersistentKeyValueKit 本身不支持异构集合。 不允许重叠的协议一致性(即对于 KeyValuePersistible),因此决定支持 Swift 中更常用的同构集合。
即
✅ KeyValuePersistible |
❌ KeyValuePersistible |
|---|---|
[Element],其中 Element: KeyValuePersistible |
[any KeyValuePersistible] |
[String: Value],其中 Value: KeyValuePersistible |
[String: any KeyValuePersistible] |
异构数组难以在 Swift 中使用:调用者需要知道每个索引处的类型。 异构字典是可以理解的(序列化键控类型),但在框架内正确支持它们并没有提供优于使用 Codable 表示的性能优势。
通过使类型符合 PrimitiveKeyValuePersistible 协议并直接与存储 API 交互,可以使用异构集合。 这是持久化键值类型的最快方法。 但是,不建议这样做,因为没有针对属性列表安全或代理表示的支持,但它是可用的。
平台不支持观察 NSUbiquitousKeyValueStore 中键的更改。 唯一的方法是监听来自其他设备的外部更改。 PersistentKeyValueKit 实现了通过框架进行的所有修改的可观察性: 任何使用 NSUbiquitousKeyValueStore 的 @PersistentValue 将自动更新 PersistentKeyValueKit 在任何地方、任何设备上所做的任何更改。 但是,在框架之外对 NSUbiquitousKeyValueStore 所做的任何更改将不会自动反映在 @PersistentValue 属性中。
PersistentKeyValueKit 目前不接受源代码贡献。 将考虑提交 Bug 报告。
PersistentKeyValueKit 在 MIT 许可下可用。
详情请参阅 LICENSE 文件。