先前版本的文档:4.0.0 版本,3.0.1 版本
迁移指南:从 4.x 到 5.x,从 4.0.0-alpha.1 到 4.0.0-alpha.3,从 3.x 到 4.x
特性 • 用法 • Codable • NSCoding • RawRepresentable • 扩展现有类型 • 自定义类型
属性包装器 • KVO • dynamicMemberLookup • 启动参数 • 实用工具 • 安装
只需一步即可开始使用 SwiftyUserDefaults
定义你的键!
extension DefaultsKeys {
var username: DefaultsKey<String?> { .init("username") }
var launchCount: DefaultsKey<Int> { .init("launchCount", defaultValue: 0) }
}
然后使用它 ;-)
// Get and set user defaults easily
let username = Defaults[\.username]
Defaults[\.hotkeyEnabled] = true
// Modify value types in place
Defaults[\.launchCount] += 1
Defaults[\.volume] -= 0.1
Defaults[\.strings] += "… can easily be extended!"
// Use and modify typed arrays
Defaults[\.libraries].append("SwiftyUserDefaults")
Defaults[\.libraries][0] += " 2.0"
// Easily work with custom serialized types
Defaults[\.color] = NSColor.white
Defaults[\.color]?.whiteComponent // => 1.0
如果你使用 Swift 5.1 - 好消息!你也可以使用 keyPath dynamicMemberLookup
Defaults.color = NSColor.white
请参阅 KeyPath dynamicMemberLookup 部分了解更多信息。
为了充分利用 SwiftyUserDefaults,请提前定义你的 user defaults 键
let colorKey = DefaultsKey<String>("color", defaultValue: "")
只需创建一个 DefaultsKey
对象,将你要存储的值的类型放在尖括号中,将键名放在圆括号中,就可以开始了。如果你想要一个非可选值,只需在键中提供一个 defaultValue
(查看上面的示例)。
你现在可以使用 Defaults
快捷方式来访问这些值
Defaults[key: colorKey] = "red"
Defaults[key: colorKey] // => "red", typed as String
编译器不会让你设置错误的值类型,并且获取操作会方便地返回 String
。
为了更加方便,通过扩展神奇的 DefaultsKeys
类并添加静态属性来定义你的键
extension DefaultsKeys {
var username: DefaultsKey<String?> { .init("username") }
var launchCount: DefaultsKey<Int> { .init("launchCount", defaultValue: 0) }
}
并使用快捷的点语法
Defaults[\.username] = "joe"
Defaults[\.launchCount] += 1
SwiftyUserDefaults 支持所有标准的 NSUserDefaults
类型,例如字符串、数字、布尔值、数组和字典。
这是一个内置的单值 defaults 的完整表格
单值 | 数组 |
---|---|
字符串 |
[字符串] |
整数 |
[整数] |
双精度浮点数 |
[双精度浮点数] |
布尔值 |
[布尔值] |
数据 |
[数据] |
日期 |
[日期] |
URL |
[URL] |
[字符串: 任意] |
[[字符串: 任意]] |
但这还不是全部!
自版本 4 以来,SwiftyUserDefaults
支持 Codable
!只需在你的类型中遵循 DefaultsSerializable
final class FrogCodable: Codable, DefaultsSerializable {
let name: String
}
无需任何实现!通过这样做,你将获得指定可选 DefaultsKey
的选项
let frog = DefaultsKey<FrogCodable?>("frog")
此外,你还可以免费获得数组支持
let froggies = DefaultsKey<[FrogCodable]?>("froggies")
NSCoding
在版本 4 之前就已受支持,但在本版本中,我们将支持提升到另一个层次。不再需要自定义下标!像支持 Codable
一样支持你的自定义 NSCoding
类型
final class FrogSerializable: NSObject, NSCoding, DefaultsSerializable { ... }
同样无需任何实现!通过这样做,你将获得指定可选 DefaultsKey
的选项
let frog = DefaultsKey<FrogSerializable?>("frog")
此外,你还可以免费获得数组支持
let froggies = DefaultsKey<[FrogSerializable]?>("froggies")
最后但并非最不重要的是,RawRepresentable
支持!同样,情况与 NSCoding
和 Codable
类似
enum BestFroggiesEnum: String, DefaultsSerializable {
case Andy
case Dandy
}
同样无需任何实现!通过这样做,你将获得指定可选 DefaultsKey
的选项
let frog = DefaultsKey<BestFroggiesEnum?>("frog")
此外,你还可以免费获得数组支持
let froggies = DefaultsKey<[BestFroggiesEnum]?>("froggies")
假设你想扩展对 UIColor
或任何其他 NSCoding
、Codable
或 RawRepresentable
类型的支持。将其扩展为 SwiftyUserDefaults
友好型应该像这样简单
extension UIColor: DefaultsSerializable {}
如果不是这样,我们有两个选择
a) 这是一个我们不知道如何序列化的自定义类型,在这种情况下,请参阅 自定义类型
b) 这是一个 bug,应该支持它,在这种情况下,请提交一个 issue(+ 你可以使用 自定义类型 方法作为临时的解决方法)
如果你想添加我们尚不支持的你自己的自定义类型,我们已经为你考虑到了。我们使用多种 DefaultsBridge
来指定你如何获取/设置值和值数组。当你查看 DefaultsSerializable
协议时,它期望每种类型都有两个属性:_defaults
和 _defaultsArray
,两者都是 DefaultsBridge
类型。
例如,这是一个用于使用 NSKeyedArchiver
/NSKeyedUnarchiver
存储/检索单值数据的 bridge
public struct DefaultsKeyedArchiverBridge<T>: DefaultsBridge {
public func get(key: String, userDefaults: UserDefaults) -> T? {
userDefaults.data(forKey: key).flatMap(NSKeyedUnarchiver.unarchiveObject) as? T
}
public func save(key: String, value: T?, userDefaults: UserDefaults) {
userDefaults.set(NSKeyedArchiver.archivedData(withRootObject: value), forKey: key)
}
public func deserialize(_ object: Any) -> T? {
guard let data = object as? Data else { return nil }
return NSKeyedUnarchiver.unarchiveObject(with: data) as? T
}
}
用于默认存储/检索数组值的 Bridge
public struct DefaultsArrayBridge<T: Collection>: DefaultsBridge {
public func save(key: String, value: T?, userDefaults: UserDefaults) {
userDefaults.set(value, forKey: key)
}
public func get(key: String, userDefaults: UserDefaults) -> T? {
userDefaults.array(forKey: key) as? T
}
public func deserialize(_ object: Any) -> T? {
nil
}
}
现在,为了在我们的类型中使用这些 bridge,我们只需按如下方式声明它
struct FrogCustomSerializable: DefaultsSerializable {
static var _defaults: DefaultsKeyedArchiverBridge( { DefaultsKeyedArchiverBridge() }
static var _defaultsArray: DefaultsKeyedArchiverBridge { DefaultsKeyedArchiverBridge() }
let name: String
}
不幸的是,如果你发现自己需要自定义 bridge,你可能需要编写自己的 bridge
final class DefaultsFrogBridge: DefaultsBridge {
func get(key: String, userDefaults: UserDefaults) -> FrogCustomSerializable? {
let name = userDefaults.string(forKey: key)
return name.map(FrogCustomSerializable.init)
}
func save(key: String, value: FrogCustomSerializable?, userDefaults: UserDefaults) {
userDefaults.set(value?.name, forKey: key)
}
func deserialize(_ object: Any) -> FrogCustomSerializable? {
guard let name = object as? String else { return nil }
return FrogCustomSerializable(name: name)
}
}
final class DefaultsFrogArrayBridge: DefaultsBridge {
func get(key: String, userDefaults: UserDefaults) -> [FrogCustomSerializable]? {
userDefaults.array(forKey: key)?
.compactMap { $0 as? String }
.map(FrogCustomSerializable.init)
}
func save(key: String, value: [FrogCustomSerializable]?, userDefaults: UserDefaults) {
let values = value?.map { $0.name }
userDefaults.set(values, forKey: key)
}
func deserialize(_ object: Any) -> [FrogCustomSerializable]? {
guard let names = object as? [String] else { return nil }
return names.map(FrogCustomSerializable.init)
}
}
struct FrogCustomSerializable: DefaultsSerializable, Equatable {
static var _defaults: DefaultsFrogBridge { DefaultsFrogBridge() }
static var _defaultsArray: DefaultsFrogArrayBridge { DefaultsFrogArrayBridge() }
let name: String
}
为了支持具有不同 bridge 的现有类型,你可以类似地扩展它
extension Data: DefaultsSerializable {
public static var _defaultsArray: DefaultsArrayBridge<[T]> { DefaultsArrayBridge() }
public static var _defaults: DefaultsDataBridge { DefaultsDataBridge() }
}
另外,查看我们的源代码(或测试)以查看更多 bridge 示例。如果你发现自己对所有这些 bridge 感到困惑,请创建一个 issue,我们将想办法解决。
SwiftyUserDefaults 为 Swift 5.1 提供了属性包装器!属性包装器 @SwiftyUserDefault
提供了使用键路径和选项(缓存或观察)的选项。
缓存 意味着我们将为你存储该值,并且几乎永远不会访问 UserDefaults
来获取值,仅在首次获取值时访问。
观察 意味着我们将通过 KVO 观察你的属性,因此你无需担心它是否在其他地方被保存以及你是否使用缓存。
现在是用法!给定键
extension DefaultsKeys {
var userColorScheme: DefaultsKey<String> { .init("userColorScheme", defaultValue: "default") }
var userThemeName: DefaultsKey<String?> { .init("userThemeName") }
var userLastLoginDate: DefaultsKey<Date?> { .init("userLastLoginDate") }
}
你可以声明一个 Settings
结构体
struct Settings {
@SwiftyUserDefault(keyPath: \.userColorScheme)
var userColorScheme: String
@SwiftyUserDefault(keyPath: \.userThemeName, options: .cached)
var userThemeName: String?
@SwiftyUserDefault(keyPath: \.userLastLoginDate, options: [.cached, .observed])
var userLastLoginDate: Date?
}
KVO 支持所有 DefaultsSerializable
类型。但是,如果你有自定义类型,则需要在其中正确定义 bridge 和序列化。
要观察本地 DefaultsKey 的值
let nameKey = DefaultsKey<String>("name", defaultValue: "")
Defaults.observe(key: nameKey) { update in
// here you can access `oldValue`/`newValue` and few other properties
}
要观察在 DefaultsKeys 扩展中定义的键的值
Defaults.observe(\.nameKey) { update in
// here you can access `oldValue`/`newValue` and few other properties
}
默认情况下,我们使用 [.old, .new]
选项进行观察,但你可以提供自己的选项
Defaults.observe(key: nameKey, options: [.initial, .old, .new]) { _ in }
SwiftyUserDefaults 使 KeyPath dynamicMemberLookup 在 Swift 5.1 中可用!
extension DefaultsKeys {
var username: DefaultsKey<String?> { .init("username") }
var launchCount: DefaultsKey<Int> { .init("launchCount", defaultValue: 0) }
}
然后使用它 ;-)
// Get and set user defaults easily
let username = Defaults.username
Defaults.hotkeyEnabled = true
// Modify value types in place
Defaults.launchCount += 1
Defaults.volume -= 0.1
Defaults.strings += "… can easily be extended!"
// Use and modify typed arrays
Defaults.libraries.append("SwiftyUserDefaults")
Defaults.libraries[0] += " 2.0"
// Easily work with custom serialized types
Defaults.color = NSColor.white
Defaults.color?.whiteComponent // => 1.0
你喜欢通过 UserDefaults 自定义你的应用/脚本/测试吗?现在我们的方面完全支持它,当然是静态类型的。
注意:目前我们仅支持 Bool
、Double
、Int
、String
值,但如果你对此功能有任何其他要求,请打开一个 issue 或 PR,我们可以讨论在新版本中实现它。
func testExample() {
let app = XCUIApplication()
app.launchArguments = ["-skipLogin", "true", "-loginTries", "3", "-lastGameTime", "61.3", "-nickname", "sunshinejr"]
app.launch()
}
./script -skipLogin true -loginTries 3 -lastGameTime 61.3 -nickname sunshinejr
要重置 user defaults,请使用 removeAll
方法。
Defaults.removeAll()
如果你在不同的应用或应用及其扩展之间共享你的 user defaults,你可以通过使用你自己的覆盖 Defaults
快捷方式来使用 SwiftyUserDefaults。只需在你的应用中添加
var Defaults = DefaultsAdapter<DefaultsKeys>(defaults: UserDefaults(suiteName: "com.my.app")!, keyStore: .init())
如果你想检查我们是否为 DefaultsKey
获取了值
let hasKey = Defaults.hasKey(\.skipLogin)
Swift 版本 >= 4.1
iOS 版本 >= 9.0
macOS 版本 >= 10.11
tvOS 版本 >= 9.0
watchOS 版本 >= 2.0
如果你使用 CocoaPods,只需将此行添加到你的 Podfile
pod 'SwiftyUserDefaults', '~> 5.0'
通过在你的终端中运行此命令来安装
pod install
然后在所有你使用它的文件中导入库
import SwiftyUserDefaults
只需添加到你的 Cartfile
github "sunshinejr/SwiftyUserDefaults" ~> 5.0
只需添加到你的 Package.swift
中的 dependencies 下
let package = Package(
name: "MyPackage",
products: [...],
dependencies: [
.package(url: "https://github.com/sunshinejr/SwiftyUserDefaults.git", .upToNextMajor(from: "5.0.0"))
],
targets: [...]
)
如果你喜欢 SwiftyUserDefaults,请查看 SwiftyTimer,它将相同的 swifty 方法应用于 NSTimer
。
你可能也会对我解释这些库背后的设计过程的博客文章感兴趣
如果你有任何意见、建议或改进的想法,请随时打开 issue 或 pull request。
维护者: Łukasz Mróz
创建者: Radek Pietruszewski
SwiftyUserDefaults 在 MIT 许可证下可用。有关更多信息,请参阅 LICENSE 文件。