快速现代的 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)DoubleCGFloatFloatStringBoolDateDataURLUUIDRangeClosedRangeCodableNSSecureCodingColor 1 (SwiftUI)Color.Resolved 1 (SwiftUI)NSColorUIColorNSFontDescriptorUIFontDescriptorDefaults 还支持上述类型包装在 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
UserDefaultslet 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中注册默认值。
DefaultsDefaults.Keys类型:class
存储键。
Defaults.Key(别名 Defaults.Keys.Key)Defaults.Key<T>(_ name: String, default: T, suite: UserDefaults = .standard)
类型:class
创建一个带有默认值的键。
默认值被写入实际的 UserDefaults 并可以在其他地方使用。例如,与 Interface Builder 绑定。
Defaults.Serializablepublic 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.Bridgepublic 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.AnySerializableDefaults.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.removeAllDefaults.removeAll(suite: UserDefaults = .standard)
类型:func
从给定的 UserDefaults 套件中删除所有条目。
Defaults.withoutPropagation(_ closure:)执行闭包而不触发变更事件。
在闭包内进行的任何 Defaults 键更改都不会传播到 Defaults 事件监听器(Defaults.observe() 和 Defaults.publisher())。当你想在监听同一个键的变化的回调中更改该键时,这可能有用,可以防止无限递归。
@Default(_ key:)获取/设置一个 Defaults 项,并在值更改时更新 SwiftUI 视图。
Defaults.CollectionSerializablepublic protocol DefaultsCollectionSerializable: Collection, Defaults.Serializable { init(_ elements: [Element]) }
类型:protocol
可以存储到原生 UserDefaults 中的 Collection。
它应该有一个初始化器 init(_ elements: [Element]) 以让 Defaults 进行反序列化。
Defaults.SetAlgebraSerializablepublic 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赋能电商视觉革命,一站式智能商拍平台
潮际好麦深耕服装行业,是国内AI试衣效果最好的软件。使用先进AIGC能力为电商卖家批量提供优质的、低成本的商拍图。合作品牌有Shein、Lazada、安踏、百丽等65个国内外头部品牌,以及国内10万+淘宝、天猫、京东等主流平台的品牌商家,为卖家节省将近85%的出图成本,提升约3倍出图效率,让品牌能够快速上架。


企业专属的AI法律顾问
iTerms是法大大集团旗下法律子品牌,基于最先进的大语言模型(LLM)、专业的法律知识库和强大的智能体架构,帮助企业扫清合规障碍,筑牢风控防线,成为您企业专属的AI法律顾问。


稳定高效的流量提升解决方案,助力品牌曝光
稳定高效的流量提升解决方案,助力品牌曝光


最新版Sora2模型免费使用,一键生成无水印视频
最新版Sora2模型免费使用,一键生成无水印视频


实时语音翻译/同声传译工具
Transly是一个多场景的AI大语言模型驱动的同声传译、专业翻译助手,它拥有超精准的音频识别翻译能力,几乎零延迟的使用体验和支持多国语言可以让你带它走遍全球,无论你是留学生、商务人士、韩剧美剧爱好者,还是出国游玩、多国会议、跨国追星等等,都可以满足你所有需要同传的场景需求,线上线下通用,扫除语言障碍,让全世界的语言交流不再有国界。


选题、配图、成文,一站式创作,让内容运营更高效
讯飞绘文,一个AI集成平台,支 持写作、选题、配图、排版和发布。高效生成适用于各类媒体的定制内容,加速品牌传播,提升内容营销效果。


AI辅助编程,代码自动修复
Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。


最强AI数据分析助手
小浣熊家族Raccoon,您的AI智能助手,致力于通过先进的人工智能技术,为用户提供高效、便捷的智能服务。无论是日常咨询还是专业问题解答,小浣熊都能以快速、准确的响应满足您的需求,让您的生活更加智能便捷。


像人一样思考的AI智能体
imini 是一款超级AI智能体,能根据人类指令,自主思考、自主完成、并且交付结果的AI智能体。


AI数字人视频创作平台
Keevx 一款开箱即用的AI数字人视频创作平台,广泛适用于电商广告、企业培训与社媒宣传,让全球企业与个人创作者无需拍摄剪辑,就能快速生成多语言、高质量的专业视频。
最新AI工具、AI资讯
独家AI资源、AI项目落地

微信扫一扫关注公众号