SwiftyUserDefaults 5.3.0

SwiftyUserDefaults 5.3.0

测试已测试
Lang语言 SwiftSwift
许可证 MIT
Released最新版本2021年2月
SPM支持 SPM

Radek PietruszewskiSunshinejr 维护。



  • Radek Pietruszewski 和 Łukasz Mróz

SwiftyUserDefaults

Stake to support us Platforms CI Status CocoaPods compatible Carthage compatible SPM compatible Swift version Swift version Swift version Swift version

NSUserDefaults 提供 Modern Swift API

SwiftyUserDefaults 通过结合表达式的 Swifty API 和静态类型的好处,使得使用用户默认项目变得愉快。在一个位置定义您的键,轻松使用值类型,并免费获得额外的安全性和方便的编译时检查。

旧版本文档:[版本 4.0.0](https://github.com/sunshinejr/SwiftyUserDefaults/blob/566ace16ee91242b61e2e9da6cdbe7dfdadd926c/README.md),[版本 3.0.1](https://github.com/sunshinejr/SwiftyUserDefaults/blob/14b629b035bf6355b46ece22c3851068a488a895/README.md)
迁移指南:[从 4.x 迁移到 5.x](MigrationGuides/migration_4_to_5.md),[从 4.0.0-alpha.1 迁移到 4.0.0-alpha.3](MigrationGuides/migration_4_alpha_1_to_4_alpha_2.md),[从 3.x 迁移到 4.x](MigrationGuides/migration_3_to_4.md)

版本 5.0.0

特性用法CodableNSCodingRawRepresentable扩展现有类型自定义类型

属性包装器KVOdynamicMemberLookup启动参数实用工具安装

特性

开始使用SwifyUserDefaults只需一步

定义您的关键字!

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 - 好消息!您还可以使用键路径 dynamicMemberLookup

Defaults.color = NSColor.white

更多内容请参见KeyPath dynamicMemberLookup部分。

用法

定义您的关键字

为了充分发挥SwifyUserDefaults的作用,请提前定义用户默认选项关键字

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]
[String: Any] [[String: Any]]

但这不仅仅如此!

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 在版本 4 之前得到了支持,但在这个版本中我们将其支持提升到了另一个层次。不再需要自定义下标!类似地,以与 Codable 支持相同的方式支持自定义的 NSCoding 类型

final class FrogSerializable: NSObject, NSCoding, DefaultsSerializable { ... }

同样无需实现!这样做你将得到一个可选的 DefaultsKey 的选项

let frog = DefaultsKey<FrogSerializable?>("frog")

此外,你还将免费获得数组支持

let froggies = DefaultsKey<[FrogSerializable]?>("froggies")

RawRepresentable

最后但同样重要的是,支持 RawRepresentable!再次,与 NSCodingCodable 一样的情形。

enum BestFroggiesEnum: String, DefaultsSerializable {
    case Andy
    case Dandy
}

同样无需实现!这样做你将得到一个可选的 DefaultsKey 的选项

let frog = DefaultsKey<BestFroggiesEnum?>("frog")

此外,你还将免费获得数组支持

let froggies = DefaultsKey<[BestFroggiesEnum]?>("froggies")

扩展现有类型

假设您想扩展一个支持 UIColor 或其他是 NSCodingCodableRawRepresentable 的类型。要使其能够与 SwiftyUserDefaults 兼容,应该非常简单:

extension UIColor: DefaultsSerializable {}

如果不是这样,我们有两个选择
a) 这是一个我们不知道如何序列化的自定义类型,在这种情况下请在 自定义类型 处添加信息
b) 这是一个bug,它应该得到支持。在这种情况下,请提交issue (+你可以在中间时间使用 自定义类型 方法作为临时解决方案)

自定义类型

如果您想添加我们尚未支持的自己的自定义类型,我们为您提供了解决方案。我们使用许多不同类型的 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
    }
}

现在,为了在我们的类型中使用这些桥接器,我们只需 declare 它如下所示:

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类型。然而,如果您有自定义类型,它需要在其中正确定义桥接和序列化。

要观察本地键的值

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使KeyPath动态成员查找在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来自定义应用程序/脚本/测试吗?现在我们全面支持这项功能,当然,支持静态类型。

注意:目前我们只支持BoolDoubleIntString值,但如果您有其他对该功能的需求,请提交一个问题或PR,我们可以讨论在新的版本中实现它。

您可以在您的模式中传递参数

Pass launch arguments in Xcode Schema editor.

或者您可以使用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()

共享用户默认值

如果您在不同的应用程序或应用程序与其扩展之间共享用户默认值,您可以使用SwiftyUserDefaults通过用自己的重写Defaults快捷方式。只需在您的应用程序中添加即可

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包管理器

只需将其添加到 dependencies 下的 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,它可以运用相同的 swifty 方法应用到 NSTimer 上。

您可能也对我的博客文章感兴趣,这些文章解释了那些库的设计过程。

贡献

如果您有评论、投诉或改进的想法,请随时提出问题或拉取请求。

作者和许可

维护者:Łukasz Mróz

创建者:Radek Pietruszewski

SwiftyUserDefaults遵循MIT许可。有关更多信息,请参阅LICENSE文件。