为 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: KeyValuePersistible
Bool
Data
Dictionary<String, Value>,其中 Value: KeyValuePersistible
Double
Float
Int
Optional<Wrapped>,其中 Wrapped: KeyValuePersistible
String
URL
有一些内置的表示形式,涵盖了应用程序最常见的用例。 它们都在这里描述。 如果需要,可以使用它们的构建块,但此处不描述。
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
文件。