AmzdSwiftyUserDefaults 5.1.0

AmzdSwiftyUserDefaults 5.1.0

Amzd维护。



  • By
  • Radek Pietruszewski and Łukasz Mróz

SwiftyUserDefaults

Platforms CI Status CocoaPods compatible Carthage compatible SPM compatible Swift version Swift version Swift version Swift version

现代 Swift API for NSUserDefaults

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

功能使用方法CodableNSCodingRawRepresentable扩展现有类型自定义类型

属性封装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!同样,情况和 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) 这是一个错误,它应该被支持,在这种情况下,请提交问题 (+您可以在同时使用 自定义类型 方法作为一个解决方案)

自定义类型

如果您想要添加我们尚未支持的自定义类型,我们已经为您做好准备。我们使用多种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 定制您的应用/脚本/测试吗?现在我们全面支持,当然是有静态类型的。

注意:目前我们只支持 BoolDoubleIntString 类型的值,但如果您有其他对该功能的要求,请打开一个 issue 或 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()

共享用户默认值

如果您想在不同的应用程序或应用程序与其扩展之间共享用户默认设置,您可以通过用自己的来覆盖 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 文件。