SwiftyUserDefaults
NSUserDefaults
现代 Swift API for SwiftyUserDefaults 通过结合表达式的 Swifty API 和静态类型的优势,使得用户默认值(UserDefaults)更容易使用。在同一个地方定义你的键,轻松使用值类型,并获得额外的安全和便捷的编译时检查。
先前版本文档:版本 4.0.0 版本 3.0.1 版本 2.0.0
迁移指南:从 4.x 转换到 5.x 从 4.0.0-alpha.1 转换到 4.0.0-alpha.3 从 3.x 转换到 4.x
版本 5.0.0
功能 • 使用方法 • Codable • NSCoding • RawRepresentable • 扩展现有类型 • 自定义类型
属性封装 • KVO • 动态成员查找 • 启动参数 • 实用工具 • 安装
特性
使用 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 动态成员查找部分。
使用方法
定义你的键
为了最大限度地利用 SwiftyUserDefaults,请提前定义你的用户默认值键
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
类型,如字符串、数字、布尔值、数组和字典。
以下是一个内置单值默认值的完整表
单值 | 数组 |
---|---|
字符串 |
[String] |
Int |
[[Int]] |
Double |
[Double] |
Bool |
[Bool] |
Data |
[[Data]] |
Date |
[[Date]] |
URL |
[[URL]] |
any [String] |
[[any [String]]] |
但这并非全部!
Codable
自版本 4 起,SwiftyUserDefaults
支持 Codable
!只需让您的类型符合 DefaultsSerializable
即可
final class FrogCodable: Codable, DefaultsSerializable {
let name: String
}
无需实现!这样就可以选择指定可选的 DefaultsKey
let frog = DefaultsKey<FrogCodable?>("frog")
此外,您还可以免费获得数组支持
let froggies = DefaultsKey<[FrogCodable]?>("froggies")
NSCoding
NSCoding
之前版本已经支持,但在本版本中我们对支持力度进行了升级。不再需要自定义下标!支持自定义的 NSCoding
类型就像支持 Codable
一样简单
final class FrogSerializable: NSObject, NSCoding, DefaultsSerializable { ... }
也不需要实现!这样您就可以选择指定可选的 DefaultsKey
let frog = DefaultsKey<FrogSerializable?>("frog")
此外,您还有免费获得数组支持的机会
let froggies = DefaultsKey<[FrogSerializable]?>("froggies")
RawRepresentable
最后但同样重要的是,支持 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) 这是一个错误,它应该被支持,在这种情况下,请提交问题 (+您可以在同时使用 自定义类型 方法作为一个解决方案)
自定义类型
如果您想要添加我们尚未支持的自定义类型,我们已经为您做好准备。我们使用多种DefaultsBridge
来指定如何获取/设置值和值数组。当您查看DefaultsSerializable
协议时,它期望在每种类型中都有两个属性:_defaults
和_defaultsArray
,它们都是DefaultsBridge
类型。
例如,这是一个使用NSKeyedArchiver
/NSKeyedUnarchiver
存储/检索单值数据的桥接器
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
}
}
用于存储/检索默认数组值的桥接器
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
}
}
现在,要在我们的类型中使用这些桥接器,我们只需声明如下:
struct FrogCustomSerializable: DefaultsSerializable {
static var _defaults: DefaultsKeyedArchiverBridge( { DefaultsKeyedArchiverBridge() }
static var _defaultsArray: DefaultsKeyedArchiverBridge { DefaultsKeyedArchiverBridge() }
let name: String
}
不幸的是,如果您发现自己处于需要自定义桥接器的情景中,您可能需要自己编写
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
}
要支持使用不同桥接器的现有类型,您可以类似地扩展它们
extension Data: DefaultsSerializable {
public static var _defaultsArray: DefaultsArrayBridge<[T]> { DefaultsArrayBridge() }
public static var _defaults: DefaultsDataBridge { DefaultsDataBridge() }
}
此外,请查看我们的源代码(或测试)以获取更多桥接器的示例。如果您对这些桥接器感到困惑,请创建一个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
KVO对所有是DefaultsSerializable
的类型都受到支持。然而,如果您有一个自定义类型,它需要在其内部正确定义桥梁和序列化。
为局部 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 }
KeyPath 动态成员查找
SwiftyUserDefaults 让学生可以在 Swift 5.1 中使用 KeyPath dynamicMemberLookup!
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,我们可以讨论在新版本中实现它。
您可以在您的架构中传递参数
或在 XCUIApplication 中使用启动参数
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
实用工具
移除所有键
要重置用户默认设置,请使用 removeAll
方法。
Defaults.removeAll()
共享用户默认值
如果您想在不同的应用程序或应用程序与其扩展之间共享用户默认设置,您可以通过用自己的来覆盖 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
如果您使用 CocoaPods,只需在 Podfile 中添加这一行
pod 'SwiftyUserDefaults', '~> 5.0'
通过在终端运行以下命令进行安装
pod install
然后在您使用库的 所有 文件中导入
import SwiftyUserDefaults
Carthage
只需将其添加到您的 Cartfile 中
github "sunshinejr/SwiftyUserDefaults" ~> 5.0
Swift Package Manager
只需在依赖项下添加到您的 Package.swift
let package = Package(
name: "MyPackage",
products: [...],
dependencies: [
.package(url: "https://github.com/sunshinejr/SwiftyUserDefaults.git", .upToNextMajor(from: "5.0.0"))
],
targets: [...]
)
更多类似内容
如果您喜欢 SwiftyUserDefaults,还可以查看 SwiftyTimer,它为 NSTimer
应用了相同的Swift方法。
您可能还对解释这些库设计过程的我的博客文章感兴趣
贡献
如果您有任何评论、投诉或改进意见,欢迎提出问题或拉取请求。
作者和许可证
维护者:Łukasz Mróz
创建者:Radek Pietruszewski
SwiftyUserDefaults受MIT许可证保护。更多信息请参阅 LICENSE 文件。