快速现代的 UserDefaults
在应用程序的多次启动之间持久存储键值对。
它底层使用 UserDefaults
,但提供了类型安全的外观和许多便利功能。
它在我所有的应用程序(超过400万用户)的生产环境中使用。
UserDefaults
值变化时更新视图的属性包装器。@AppStorage
的优势Codable
。Toggle
组件。在 Xcode 的 "Swift Package Manager" 标签中添加 https://github.com/sindresorhus/Defaults
。
Int(8/16/32/64)
UInt(8/16/32/64)
Double
CGFloat
Float
String
Bool
Date
Data
URL
UUID
Range
ClosedRange
Codable
NSSecureCoding
Color
1 (SwiftUI)Color.Resolved
1 (SwiftUI)NSColor
UIColor
NSFontDescriptor
UIFontDescriptor
Defaults 还支持上述类型包装在 Array
、Set
、Dictionary
、Range
、ClosedRange
中,甚至包装在嵌套类型中。例如,[[String: Set<[String: Int]>]]
。
更多类型,请参见枚举示例、Codable
示例或高级用法。更多示例,请参见 Tests/DefaultsTests。
你可以轻松地为任何自定义类型添加支持。
如果一个类型同时遵循 NSSecureCoding
和 Codable
,则将使用 Codable
进行序列化。
你需要预先声明默认值键,包括类型和默认值。
键名必须是 ASCII,不能以 @
开头,并且不能包含点 (.
)。
import Defaults extension Defaults.Keys { static let quality = Key<Double>("quality", default: 0.8) // ^ ^ ^ ^ // 键 类型 UserDefaults 名称 默认值 }
然后你可以通过 Defaults
全局对象的下标访问它:
Defaults[.quality] //=> 0.8 Defaults[.quality] = 0.5 //=> 0.5 Defaults[.quality] += 0.1 //=> 0.6 Defaults[.quality] = "🦄" //=> [Cannot assign value of type 'String' to type 'Double']
你也可以声明可选键,当你不想预先声明默认值时:
extension Defaults.Keys { static let name = Key<Double?>("name") } if let name = Defaults[.name] { print(name) }
此时默认值为 nil
。
你还可以指定动态默认值。这在默认值可能在应用程序生命周期内改变时很有用:
extension Defaults.Keys { static let camera = Key<AVCaptureDevice?>("camera") { .default(for: .video) } }
enum DurationKeys: String, Defaults.Serializable { case tenMinutes = "10 Minutes" case halfHour = "30 Minutes" case oneHour = "1 Hour" } extension Defaults.Keys { static let defaultDuration = Key<DurationKeys>("defaultDuration", default: .oneHour) } Defaults[.defaultDuration].rawValue //=> "1 Hour"
(只要枚举的原始值是任何支持的类型,这就可以工作)
struct User: Codable, Defaults.Serializable { let name: String let age: String } extension Defaults.Keys { static let user = Key<User>("user", default: .init(name: "Hello", age: "24")) } Defaults[.user].name //=> "Hello"
你不必将键附加到 Defaults.Keys
。
let isUnicorn = Defaults.Key<Bool>("isUnicorn", default: true) Defaults[isUnicorn] //=> true
@Default
你可以使用 @Default
属性包装器来获取/设置 Defaults
项,并在值变化时更新视图。这类似于 @State
。
extension Defaults.Keys { static let hasUnicorn = Key<Bool>("hasUnicorn", default: false) } struct ContentView: View { @Default(.hasUnicorn) var hasUnicorn var body: some View { Text("Has Unicorn: \(hasUnicorn)") Toggle("Toggle", isOn: $hasUnicorn) Button("Reset") { _hasUnicorn.reset() } } }
注意是 @Default
,而不是 @Defaults
。
你不能在 ObservableObject
中使用 @Default
。它是为在 View
中使用而设计的。
Toggle
还有一个 SwiftUI.Toggle
包装器,可 以更容易地创建基于 Defaults
键的布尔值切换。
extension Defaults.Keys { static let showAllDayEvents = Key<Bool>("showAllDayEvents", default: false) } struct ShowAllDayEventsSetting: View { var body: some View { Defaults.Toggle("Show All-Day Events", key: .showAllDayEvents) } }
你也可以监听变化:
struct ShowAllDayEventsSetting: View { var body: some View { Defaults.Toggle("显示全天事件", key: .showAllDayEvents) // 注意这必须直接附加到 `Defaults.Toggle`。它不是 `View#onChange()`。 .onChange { print("值", $0) } } }
extension Defaults.Keys { static let isUnicornMode = Key<Bool>("isUnicornMode", default: false) } // … Task { for await value in Defaults.updates(.isUnicornMode) { print("值:", value) } }
与原生 UserDefaults
键观察相比,这里你会收到一个强类型的变化对象。
extension Defaults.Keys { static let isUnicornMode = Key<Bool>("isUnicornMode", default: false) } Defaults[.isUnicornMode] = true //=> true Defaults.reset(.isUnicornMode) Defaults[.isUnicornMode] //=> false
这对可选类型的 Key
也适用,它会被重置回 nil
。
在 Defaults.withoutPropagation
闭包中进行的更改不会传播到观察回调(Defaults.observe()
或 Defaults.publisher()
),因此可以防止无限递归。
let observer = Defaults.observe(keys: .key1, .key2) { // … Defaults.withoutPropagation { // 更新 `.key1` 而不将变化传播给监听器。 Defaults[.key1] = 11 } // 这个会被传播。 Defaults[.someKey] = true }
UserDefaults
这也可以工作:
extension Defaults.Keys { static let isUnicorn = Key<Bool>("isUnicorn", default: true) } UserDefaults.standard[.isUnicorn] //=> true
UserDefaults
let extensionDefaults = UserDefaults(suiteName: "com.unicorn.app")! extension Defaults.Keys { static let isUnicorn = Key<Bool>("isUnicorn", default: true, suite: extensionDefaults) } Defaults[.isUnicorn] //=> true // 或者 extensionDefaults[.isUnicorn] //=> true
UserDefaults
当你创建一个 Defaults.Key
时,它会自动将 default
值注册到普通的 UserDefaults
中。这意味着你可以在例如 Interface Builder 的绑定中使用默认值。
extension Defaults.Keys { static let isUnicornMode = Key<Bool>("isUnicornMode", default: true) } print(UserDefaults.standard.bool(forKey: Defaults.Keys.isUnicornMode.name)) //=> true
注意 具有动态默认值的
Defaults.Key
不会在UserDefaults
中注册默认值。
Defaults
Defaults.Keys
类型:class
存储键。
Defaults.Key
(别名 Defaults.Keys.Key
)Defaults.Key<T>(_ name: String, default: T, suite: UserDefaults = .standard)
类型:class
创建一个带有默认值的键。
默认值被写入实际的 UserDefaults
并可以在其他地方使用。例如,与 Interface Builder 绑定。
Defaults.Serializable
public protocol DefaultsSerializable { typealias Value = Bridge.Value typealias Serializable = Bridge.Serializable associatedtype Bridge: Defaults.Bridge static var bridge: Bridge { get } }
类型:protocol
遵循此协议的类型可以与 Defaults
一起使用。
该类型应该有一个静态变量 bridge
,它应该引用一个遵循 Defaults.Bridge
的类型实例。
Defaults.Bridge
public protocol DefaultsBridge { associatedtype Value associatedtype Serializable func serialize(_ value: Value?) -> Serializable? func deserialize(_ object: Serializable?) -> Value? }
类型:protocol
Bridge
负责序列化和反序列化。
它有两个关联类型 Value
和 Serializable
。
Value
:你想要使用的类型。Serializable
:存储在 UserDefaults
中的类型。serialize
:在存储到 UserDefaults
之前执行。deserialize
:从 UserDefaults
检索其值后执行。Defaults.AnySerializable
Defaults.AnySerializable<Value: Defaults.Serializable>(_ value: Value)
类型:class
Defaults.Serializable
值的类型擦除包装器。
get<Value: Defaults.Serializable>() -> Value?
:从 UserDefaults 中检索类型为 Value
的值。get<Value: Defaults.Serializable>(_: Value.Type) -> Value?
:指定你想要检索的 Value
。这在某些模糊情况下可能有用。set<Value: Defaults.Serializable>(_ newValue: Value)
:为 Defaults.AnySerializable
设置新值。Defaults.reset(keys…)
类型:func
将给定的键重置回它们的默认值。
你也可以指定字符串键,这在你需要在集合中存储一些键时可能很有用,因为由于 Defaults.Key
是泛型,所以无法在集合中存储它。
Defaults.removeAll
Defaults.removeAll(suite: UserDefaults = .standard)
类型:func
从给定的 UserDefaults
套件中删除所有条目。
Defaults.withoutPropagation(_ closure:)
执行闭包而不触发变更事件。
在闭包内进行的任何 Defaults
键更改都不会传播到 Defaults
事件监听器(Defaults.observe()
和 Defaults.publisher()
)。当你想在监听同一个键的变化的回调中更改该键时,这可能有用,可以防止无限递归。
@Default(_ key:)
获取/设置一个 Defaults
项,并在值更改时更新 SwiftUI 视图。
Defaults.CollectionSerializable
public protocol DefaultsCollectionSerializable: Collection, Defaults.Serializable { init(_ elements: [Element]) }
类型:protocol
可以存储到原生 UserDefaults
中的 Collection
。
它应该有一个初始化器 init(_ elements: [Element])
以让 Defaults
进行反序列化。
Defaults.SetAlgebraSerializable
public protocol DefaultsSetAlgebraSerializable: SetAlgebra, Defaults.Serializable { func toArray() -> [Element] }
类型:protocol
可以存储到原生 UserDefaults
中的 SetAlgebra
。
它应该有一个函数 func toArray() -> [Element]
以让 Defaults
进行序列化。
虽然 Defaults
已经内置支持许多类型,但你可能需要使用自己的自定义类型。以下指南将向你展示如何使自己的自定义类型与 Defaults
一起工作。
struct User { let name: String let age: String }
Defaults.Bridge
的桥接器,负责处理序列化和反序列化。struct UserBridge: Defaults.Bridge { typealias Value = User typealias Serializable = [String: String] public func serialize(_ value: Value?) -> Serializable? { guard let value else { return nil } return [ "name": value.name, "age": value.age ] } public func deserialize(_ object: Serializable?) -> Value? { guard let object, let name = object["name"], let age = object["age"] else { return nil } return User( name: name, age: age ) } }
User
的扩展,使其符合 Defaults.Serializable
。它的静态桥接器应该是我们上面创建的桥接器。struct User { let name: String let age: String } extension User: Defaults.Serializable { static let bridge = UserBridge() }
extension Defaults.Keys { static let user = Defaults.Key<User>("user", default: User(name: "Hello", age: "24")) static let arrayUser = Defaults.Key<[User]>("arrayUser", default: [User(name: "Hello", age: "24")]) static let setUser = Defaults.Key<Set<User>>("user", default: Set([User(name: "Hello", age: "24")])) static let dictionaryUser = Defaults.Key<[String: User]>("dictionaryUser", default: ["user": User(name: "Hello", age: "24")]) } Defaults[.user].name //=> "Hello" Defaults[.arrayUser][0].name //=> "Hello" Defaults[.setUser].first?.name //=> "Hello" Defaults[.dictionaryUser]["user"]?.name //=> "Hello"
可能会有一些情况,你想直接使用 [String: Any]
,但 Defaults
需要其值符合 Defaults.Serializable
。类型擦除器 Defaults.AnySerializable
可以帮助克服这个限制。
Defaults.AnySerializable
仅适用于符合 Defaults.Serializable
的值。
警告:类型擦除器应该只在没有其他方法处理时使用,因为它的性能要差得多。它应该只用于包装类型中。例如,包装在 Array
、Set
或 Dictionary
中。
Defaults.AnySerializable
符合 ExpressibleByStringLiteral
、ExpressibleByIntegerLiteral
、ExpressibleByFloatLiteral
、ExpressibleByBooleanLiteral
、ExpressibleByNilLiteral
、ExpressibleByArrayLiteral
和 ExpressibleByDictionaryLiteral
。
这意味着你可以直接赋值这些基本类型:
let any = Defaults.Key<Defaults.AnySerializable>("anyKey", default: 1) Defaults[any] = "🦄"
get
和 set
对于其他类型,你需要这样赋值:
enum mime: String, Defaults.Serializable { case JSON = "application/json" case STREAM = "application/octet-stream" } let any = Defaults.Key<Defaults.AnySerializable>("anyKey", default: [Defaults.AnySerializable(mime.JSON)]) if let mimeType: mime = Defaults[any].get() { print(mimeType.rawValue) //=> "application/json" } Defaults[any].set(mime.STREAM) if let mimeType: mime = Defaults[any].get() { print(mimeType.rawValue) //=> "application/octet-stream" }
Array
、Set
或 Dictionary
中Defaults.AnySerializable
也支持上述类型包装在 Array
、Set
、Dictionary
中。
这里是 [String: Defaults.AnySerializable]
的示例:
extension Defaults.Keys { static let magic = Key<[String: Defaults.AnySerializable]>("magic", default: [:]) } enum mime: String, Defaults.Serializable { case JSON = "application/json" } // … Defaults[.magic]["unicorn"] = "🦄" if let value: String = Defaults[.magic]["unicorn"]?.get() { print(value) //=> "🦄" } Defaults[.magic]["number"] = 3 Defaults[.magic]["boolean"] = true Defaults[.magic]["enum"] = Defaults.AnySerializable(mime.JSON) if let mimeType: mime = Defaults[.magic]["enum"]?.get() { print(mimeType.rawValue) //=> "application/json" }
更多示例,请参见 Tests/DefaultsAnySerializableTests。
Codable
类型的序列化你可能有一个符合 Codable & NSSecureCoding
或 Codable & RawRepresentable
枚举的类 型。默认情况下,Defaults
会优先使用 Codable
一致性,并使用 CodableBridge
将其序列化为 JSON 字符串。如果你想将其序列化为 NSSecureCoding
数据或使用 RawRepresentable
枚举的原始值,你可以遵循 Defaults.PreferNSSecureCoding
或 Defaults.PreferRawRepresentable
来覆盖默认桥接器:
enum mime: String, Codable, Defaults.Serializable, Defaults.PreferRawRepresentable { case JSON = "application/json" } extension Defaults.Keys { static let magic = Key<[String: Defaults.AnySerializable]>("magic", default: [:]) } print(UserDefaults.standard.string(forKey: "magic")) //=> application/json
如果我们没有添加 Defaults.PreferRawRepresentable
,存储的表示将会是 "application/json"
而不是 application/json
。
如果你让一个你无法控制的类型遵循 Defaults.Serializable
,这也可能很有用,因为该类型可能随时获得 Codable
一 致性,然后存储的表示就会改变,这可能会导致该值不可读。通过明确定义使用哪个桥接器,你可以确保存储的表示始终保持不变。
Collection
类型Collection
,并使其元素符合 Defaults.Serializable
。struct Bag<Element: Defaults.Serializable>: Collection { var items: [Element] var startIndex: Int { items.startIndex } var endIndex: Int { items.endIndex } mutating func insert(element: Element, at: Int) { items.insert(element, at: at) } func index(after index: Int) -> Int { items.index(after: index) } subscript(position: Int) -> Element { items[position] } }
Bag
的扩展,使其符合 Defaults.CollectionSerializable
。extension Bag: Defaults.CollectionSerializable { init(_ elements: [Element]) { self.items = elements } }
extension Defaults.Keys { static let stringBag = Key<Bag<String>>("stringBag", default: Bag(["Hello", "World!"])) }
Defaults[.stringBag][0] //=> "Hello" Defaults[.stringBag][1] //=> "World!"
### 自定义 `SetAlgebra` 类型
1. 创建你的 `SetAlgebra` 并使其元素符合 `Defaults.Serializable & Hashable`
```swift
struct SetBag<Element: Defaults.Serializable & Hashable>: SetAlgebra {
var store = Set<Element>()
init() {}
init(_ store: Set<Element>) {
self.store = store
}
func contains(_ member: Element) -> Bool {
store.contains(member)
}
func union(_ other: SetBag) -> SetBag {
SetBag(store.union(other.store))
}
func intersection(_ other: SetBag) -> SetBag {
var setBag = SetBag()
setBag.store = store.intersection(other.store)
return setBag
}
func symmetricDifference(_ other: SetBag) -> SetBag {
var setBag = SetBag()
setBag.store = store.symmetricDifference(other.store)
return setBag
}
@discardableResult
mutating func insert(_ newMember: Element) -> (inserted: Bool, memberAfterInsert: Element) {
store.insert(newMember)
}
mutating func remove(_ member: Element) -> Element? {
store.remove(member)
}
mutating func update(with newMember: Element) -> Element? {
store.update(with: newMember)
}
mutating func formUnion(_ other: SetBag) {
store.formUnion(other.store)
}
mutating func formSymmetricDifference(_ other: SetBag) {
store.formSymmetricDifference(other.store)
}
mutating func formIntersection(_ other: SetBag) {
store.formIntersection(other.store)
}
}
Defaults.SetAlgebraSerializable
的 SetBag
扩展extension SetBag: Defaults.SetAlgebraSerializable { func toArray() -> [Element] { Array(store) } }
extension Defaults.Keys { static let stringSet = Key<SetBag<String>>("stringSet", default: SetBag(["Hello", "World!"])) } Defaults[.stringSet].contains("Hello") //=> true Defaults[.stringSet].contains("World!") //=> true
在 Defaults
v5 之后,你不需要使用 Codable
来存储字典,Defaults
原生支持存储字典。
关于 Defaults
支持的类型,请参见支持的类型。
SwiftyUserDefaults
有什么不同?它受到该包和其他解决方案的启发。主要区别在于该模块不硬编码默认值,并提供 Codable 支持。
前任
AI辅助编程,代码自动修复
Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。
AI小说写作助手,一站式润色、改写、扩写
蛙蛙写作—国内先进的AI写作平台,涵盖小说、学术、社交媒体等多场景。提供续写、改写、润色等功能,助力创作者高效优化写作流程。界面简洁,功能全面,适合各类写作者提升内容品质和工作效率。
全能AI智能助手,随时解答生活与工作的多样问题
问小白,由元石科技研发的AI智能助手,快速准确地解答各种生活和工作问题,包括但不限于搜索、规划和社交互动,帮助用户在日常生活中提高效率,轻松管理个人事务。
实时语音翻译/同声传译工具
Transly是一个多场景的AI大语言模型驱动的同声传译、专业翻译助手,它拥有超精准的音频识别翻译能力,几乎零延迟的使用体验和支持多国语言可以让你带它走遍全球,无论你是留学生、商务人士、韩剧美剧爱好者,还是出国游玩、多国会议、跨国追星等等,都可以满足你所有需要同传的场景需求,线上线下通用,扫除语言障碍,让全世界的语言交流不再有国界。
一键生成PPT和Word,让学习生活更轻松
讯飞智文是一个利用 AI 技术的项目,能够帮助用户生成 PPT 以及各类文档。无论是商业领域的市场分析报告、年度目标制定,还是学生群体的职业生涯规划、实习避坑指南,亦或是活动策划、旅游攻略等内容,它都能提供支持,帮助用户精准表达,轻松呈现各种信息。
深度推理能力全新升级,全面对标OpenAI o1
科大讯飞的星火大模型,支持语言理解、知识问答和文本创作等多功能,适用于多种文件和业务场景,提升办公和日常生活的效率。讯飞星火是一个提供丰富智能服务的平台,涵盖科技资讯、图像创作、写作辅助、编程解答、科研文献解读等功能,能为不同需求的用户提供便捷高效的帮助,助力用户轻松获取信息、解决问题,满足多样化使用场景。
一种基于大语言模型的高效单流解耦语音令牌文本到语音合成模型
Spark-TTS 是一个基于 PyTorch 的开源文本到语音合成项目,由多个知名机构联合参与。该项目提供了高效的 LLM(大语言模型)驱动的语音合成方案,支持语音克隆和语音创建功能,可通过命令行界面(CLI)和 Web UI 两种方式使用。用户可以根据需求调整语音的性别、音高、速度等参数,生成高质量的语音。该项目适用于多种场景,如有声读物制作、智能语音助手开发等。
AI助力,做PPT更简单!
咔片是一款轻量化在线演示设计工具,借助 AI 技术,实现从内容生成到智能设计的一站式 PPT 制作服务。支持多种文档格式导入生成 PPT,提供海量模板、智能美化、素材替换等功能,适用于销售、教师、学生等各类人群,能高效制作出高品质 PPT,满足不同场景演示需求。
选题、配图、成文,一站式创作,让内容运营更高效
讯飞绘文,一个AI集成平台,支持写作、选题、配图、排版和发布。高效生成适用于各类媒体的定制内容,加速品牌传播,提升内容营销效果。
专业的AI公文写作平台,公文写作神器
AI 材料星,专业的 AI 公文写作辅助平台,为体制内工作人员提供高效的公文写作解决方案。拥有海量公文文库、9 大核心 AI 功能,支持 30 + 文稿类型生成,助力快速完成领导讲话、工作总结、述职报告等材料,提升办公效率,是体制打工人的得力写作神器。
最新AI工具、AI资讯
独家AI资源、AI项目落地
微信扫一扫关注公众号