Swiftpack.co - Package - radex/SwiftyUserDefaults

SwiftyUserDefaults

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

Modern Swift API for NSUserDefaults

SwiftyUserDefaults makes user defaults enjoyable to use by combining expressive Swifty API with the benefits of static typing. Define your keys in one place, use value types easily, and get extra safety and convenient compile-time checks for free.

Read documentation for stable version 3.0.1
Read migration guide from version 3.x to 4.x
Read migration guide from version 4.0.0-alpha.1 to 4.0.0-alpha.3

Version 4 - beta 2

FeaturesUsageCodableNSCodingRawRepresentableExtending existing typesCustom typesKVOLaunch argumentsUtilsInstallation

Features

There's only one step to start using SwiftyUserDefaults:

Define your keys!

extension DefaultsKeys {
    static let username = DefaultsKey<String?>("username")
    static let launchCount = DefaultsKey<Int>("launchCount", defaultValue: 0)
}

And just use it ;-)

// 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

The convenient dot syntax is only available if you define your keys by extending magic DefaultsKeys class. You can also just pass the DefaultsKey value in square brackets.

Usage

Define your keys

To get the most out of SwiftyUserDefaults, define your user defaults keys ahead of time:

let colorKey = DefaultsKey<String>("color", defaultValue: "")

Just create a DefaultsKey object, put the type of the value you want to store in angle brackets, the key name in parentheses, and you're good to go. If you want to have a non-optional value, just provide a defaultValue in the key (look at the example above).

You can now use the Defaults shortcut to access those values:

Defaults[colorKey] = "red"
Defaults[colorKey] // => "red", typed as String

The compiler won't let you set a wrong value type, and fetching conveniently returns String.

Take shortcuts

For extra convenience, define your keys by extending magic DefaultsKeys class and adding static properties:

extension DefaultsKeys {
    static let username = DefaultsKey<String?>("username")
    static let launchCount = DefaultsKey<Int>("launchCount", defaultValue: 0)
}

And use the shortcut dot syntax:

Defaults[.username] = "joe"
Defaults[.launchCount] += 1

Supported types

SwiftyUserDefaults supports all of the standard NSUserDefaults types, like strings, numbers, booleans, arrays and dictionaries.

Here's a full table of built-in single value defaults:

| Single value | Array | | ---------------- | -------------------- | | String | [String] | | Int | [Int] | | Double | [Double] | | Bool | [Bool] | | Data | [Data] | | Date | [Date] | | URL | [URL] | | [String: Any] | [[String: Any]] |

But that's not all!

Codable

Since version 4, SwiftyUserDefaults support Codable! Just conform to DefaultsSerializable in your type:

final class FrogCodable: Codable, DefaultsSerializable {
    let name: String
 }

No implementation needed! By doing this you will get an option to specify an optional DefaultsKey:

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

Additionally, you've got an array support for free:

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

NSCoding

NSCoding was supported before version 4, but in this version we take the support on another level. No need for custom subscripts anymore! Support your custom NSCoding type the same way as with Codable support:

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

No implementation needed as well! By doing this you will get an option to specify an optional DefaultsKey:

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

Additionally, you've got an array support also for free:

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

RawRepresentable

And the last but not least, RawRepresentable support! Again, the same situation like with NSCoding and Codable:

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

No implementation needed as well! By doing this you will get an option to specify an optional DefaultsKey:

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

Additionally, you've got an array support also for free:

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

Extending existing types

Let's say you want to extend a support UIColor or any other type that is NSCoding, Codable or RawRepresentable. Extending it to be SwiftyUserDefaults-friendly should be as easy as:

extension UIColor: DefaultsSerializable {}

If it's not, we have two options:
a) It's a custom type that we don't know how to serialize, in this case at Custom types
b) It's a bug and it should be supported, in this case please file an issue (+ you can use custom types method as a workaround in the meantime)

Custom types

If you want to add your own custom type that we don't support yet, we've got you covered. We use DefaultsBridges of many kinds to specify how you get/set values and arrays of values. When you look at DefaultsSerializable protocol, it expects two properties in each type: _defaults and _defaultsArray, where both are of type DefaultsBridge.

For instance, this is a bridge for single value data storing/retrieving using NSKeyedArchiver/NSKeyedUnarchiver:

public final class DefaultsKeyedArchiverBridge<T>: DefaultsBridge<T> {

    public override func get(key: String, userDefaults: UserDefaults) -> T? {
        return userDefaults.data(forKey: key).flatMap(NSKeyedUnarchiver.unarchiveObject) as? T
    }

    public override func save(key: String, value: T?, userDefaults: UserDefaults) {
        userDefaults.set(NSKeyedArchiver.archivedData(withRootObject: value), forKey: key)
    }
}

Bridge for default storing/retrieving array values:

public final class DefaultsArrayBridge<T: Collection>: DefaultsBridge<T> {
    public override func save(key: String, value: T?, userDefaults: UserDefaults) {
        userDefaults.set(value, forKey: key)
    }

    public override func get(key: String, userDefaults: UserDefaults) -> T? {
        return userDefaults.array(forKey: key) as? T
    }
}

Now, to use these bridges in our type we simply declare it as follows:

struct FrogCustomSerializable: DefaultsSerializable {

    static var _defaults: DefaultsBridge<FrogCustomSerializable> { return DefaultsKeyedArchiverBridge() }
    static var _defaultsArray: DefaultsBridge<[FrogCustomSerializable]> { return DefaultsKeyedArchiverBridge() }

    let name: String
}

Unfortunately, if you find yourself in a situation where you need a custom bridge, you'll probably need to write your own:

final class DefaultsFrogBridge: DefaultsBridge<FrogCustomSerializable> {
    override func get(key: String, userDefaults: UserDefaults) -> FrogCustomSerializable? {
        let name = userDefaults.string(forKey: key)
        return name.map(FrogCustomSerializable.init)
    }

    override func save(key: String, value: FrogCustomSerializable?, userDefaults: UserDefaults) {
        userDefaults.set(value?.name, forKey: key)
    }

    public override func isSerialized() -> Bool {
        return true
    }

    public override func deserialize(_ object: Any) -> FrogCustomSerializable? {
        guard let name = object as? String else { return nil }

        return FrogCustomSerializable(name: name)
    }
}

final class DefaultsFrogArrayBridge: DefaultsBridge<[FrogCustomSerializable]> {
    override func get(key: String, userDefaults: UserDefaults) -> [FrogCustomSerializable]? {
        return userDefaults.array(forKey: key)?
            .compactMap { $0 as? String }
            .map(FrogCustomSerializable.init)
    }

    override func save(key: String, value: [FrogCustomSerializable]?, userDefaults: UserDefaults) {
        let values = value?.map { $0.name }
        userDefaults.set(values, forKey: key)
    }

    public override func isSerialized() -> Bool {
        return true
    }

    public override 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: DefaultsBridge<FrogCustomSerializable> { return DefaultsFrogBridge() }
    static var _defaultsArray: DefaultsBridge<[FrogCustomSerializable]> { return DefaultsFrogArrayBridge() }

    let name: String
}

To support existing types with different bridges, you can extend it similarly:

extension Data: DefaultsSerializable {
    public static var _defaults: DefaultsBridge<Data> { return DefaultsDataBridge() }
    public static var _defaultsArray: DefaultsBridge<[Data]> { return DefaultsArrayBridge() }
}

Also, take a look at our source code (or tests) to see more examples of bridges. If you find yourself confused with all these bridges, please create an issue and we will figure something out.

KVO

KVO is supported for all the types that are DefaultsSerializable. However, if you have a custom type, it needs to have correctly defined bridges and serialization in them.

To observe a value:

let nameKey = DefaultsKey<String>("name", defaultValue: "")
Defaults.observe(key: nameKey) { update in
	// here you can access `oldValue`/`newValue` and few other properties
}

By default we are using [.old, .new] options for observing, but you can provide your own:

Defaults.observe(key: nameKey, options: [.initial, .old, .new]) { _ in }

Launch arguments

Do you like to customize your app/script/tests by UserDefaults? Now it's fully supported on our side, statically typed of course.

Note: for now we support only Bool, Double, Int, String values, but if you have any other requests for that feature, please open an issue or PR and we can talk about implementing it in new versions.

You can pass your arguments in your schema:

Pass launch arguments in Xcode Schema editor.

Or you can use launch arguments in XCUIApplication:

func testExample() {
    let app = XCUIApplication()
    app.launchArguments = ["-skipLogin", "true", "-loginTries", "3", "-lastGameTime", "61.3", "-nickname", "sunshinejr"]
    app.launch()
}

Or pass them as command line arguments!

./script -skipLogin true -loginTries 3 -lastGameTime 61.3 -nickname sunshinejr

Utils

Remove all keys

To reset user defaults, use removeAll method.

Defaults.removeAll()

Shared user defaults

If you're sharing your user defaults between different apps or an app and its extensions, you can use SwiftyUserDefaults by overriding the Defaults shortcut with your own. Just add in your app:

var Defaults = UserDefaults(suiteName: "com.my.app")!

Check key

If you want to check if we've got a value for DefaultsKey:

let hasKey = Defaults.hasKey(.skipLogin)

Installation

Requirements

Swift version >= 4.1
iOS version >= 8.0
macOS version >= 10.11
tvOS version >= 9.0
watchOS version >= 2.0

CocoaPods

If you're using CocoaPods, just add this line to your Podfile:

pod 'SwiftyUserDefaults', '4.0.0-beta.2'

Install by running this command in your terminal:

pod install

Then import the library in all files where you use it:

import SwiftyUserDefaults

Carthage

Just add to your Cartfile:

github "radex/SwiftyUserDefaults" "4.0.0-beta.2"

Swift Package Manager

Just add to your Package.swift under dependencies:

let package = Package(
    name: "MyPackage",
    products: [...],
    dependencies: [
        .package(url: "https://github.com/radex/SwiftyUserDefaults.git", .exact("4.0.0-beta.2")),
    ],
    targets: [...]
)

More like this

If you like SwiftyUserDefaults, check out SwiftyTimer, which applies the same swifty approach to NSTimer.

You might also be interested in my blog posts which explain the design process behind those libraries:

Contributing

If you have comments, complaints or ideas for improvements, feel free to open an issue or a pull request.

Authors and license

Maintainer: ลukasz Mrรณz

Created by: Radek Pietruszewski

SwiftyUserDefaults is available under the MIT license. See the LICENSE file for more info.

Github

link
Stars: 3716
Help us keep the lights on

Dependencies

Releases

4.0.0-beta.2 - Mar 9, 2019

Minor stability update for Carthage users. This should fix the linking problem we've had in beta.1. No other fixes/updates were introduced in here.

4.0.0-beta.1 - Feb 25, 2019

This update introduces few cool additions to the library! ๐ŸŽ‰

Changelog

  • Added support for launch arguments/plist for Bool, Double, Int, String values. @sunshinejr
  • Added support for KVO! DivineDominion, toshi0383, @sunshinejr
  • Brought back dictionary support for [String: Any]/[String: String] and corresponding array version of it [[String: Any]]/[[String: String]]. @sunshinejr

If you have already created a custom bridge for you type, and you want to use it with KVO, you'll need to override two new functions: isSerialized() and deserialize(_:). See Readme for more instructions.

4.0.0-alpha.3 - Feb 19, 2019

This is a small update over 4.0.0-alpha.2 with two fixes.

Migration guide from 3.x to 4.x Migration guide from 4.0.0-alpha.1 to 4.0.0-alpha.2

Changelog

4.0.0-alpha.2 - Feb 18, 2019

๐ŸŽ‰๐ŸŽ‰๐ŸŽ‰

We're back with alpha 2!

Migration guide from 3.x to 4.x Migration guide from 4.0.0-alpha.1 to 4.0.0-alpha.2

Changelog

  • Swift 4.2 support. @sunshinejr
  • Early Swift 5.0 support! @sunshinejr
  • Rewritten core. We use DefaultsBridges now to define getters/setters for given type. @sunshinejr
  • Fixed a bug where you couldn't extend non-final class like NSColor. @sunshinejr
  • Removed type-based default values. This means you need to use key-based defaultValue or use an optional DefaultsKey from now on. @sunshinejr
  • Improved CI infra: Swift 4.1/4.2/5.0 builds with CocoaPods/Carthage/SPM integration scripts. @sunshinejr

4.0.0-alpha.1 - May 8, 2018

๐ŸŽ‰๐ŸŽ‰๐ŸŽ‰

This is a huge release. Please try it out and let me know how it feels as we still have time for improvements!

Migration guide from 3.x to 4.x

Changelog

  • Swift 4.1 support @sunshinejr
  • Added Codable support! @sunshinejr
  • Added generic subscripts support (better DefaultsKey init diagnostics and accessing Defaults[.key]) @sunshinejr
  • Added default values protocols (DefaultsDefaultValueType, DefaultsDefaultArrayValueType) - this means that you can extend any type with default value so you can create non-optional DefaultsKey afterwards! @sunshinejr
  • Added default values in DefaultsKey, e.g. DefaultsKey<String>("test", defaultValue: "default value") @sunshinejr
  • Added better support for custom types: using DefaultsSerializable, when your type implements NSCoding, RawRepresentable (enums as well) or Codable, you get default implementations for free! @sunshinejr
  • Added automatic array support for any type that is available to SwiftyUserDefaults (means custom with DefaultsSerializable as well!) @sunshinejr
  • Added Swift Package Manager support! @sunshinejr
  • Added [URL] built-in support! @sunshinejr
  • A lot of infrastructure changes (CI, project), around 350 tests to make sure all of the changes work properly! @sunshinejr
  • Removed legacy strings based API (Defaults["test"]), Dictionary and Any support (sorry, with all the changes in the library we had to, but you probably can bring it back with DefaultsSerializable anyways ๐Ÿ˜…) @sunshinejr