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.
Previous versions' documentation: Version 4.0.0, Version 3.0.1
Migration guides: from 4.x to 5.x, from 4.0.0-alpha.1 to 4.0.0-alpha.3, from 3.x to 4.x
Features • Usage • Codable • NSCoding • RawRepresentable • Extending existing types • Custom types
Property wrappers • KVO • dynamicMemberLookup • Launch arguments • Utils • Installation
There's only one step to start using SwiftyUserDefaults:
Define your keys!
extensionDefaultsKeys{varusername:DefaultsKey<String?>{.init("username")}varlaunchCount:DefaultsKey<Int>{.init("launchCount", defaultValue:0)}}And just use it ;-)
// Get and set user defaults easily
letusername=Defaults[\.username]Defaults[\.hotkeyEnabled]=true
// Modify value types in place
Defaults[\.launchCount]+=1Defaults[\.volume]-=0.1Defaults[\.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.0If you use Swift 5.1 - good news! You can also use keyPath dynamicMemberLookup:
Defaults.color =NSColor.whiteSee more at the KeyPath dynamicMemberLookup section.
To get the most out of SwiftyUserDefaults, define your user defaults keys ahead of time:
letcolorKey=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[key: colorKey]="red"Defaults[key: colorKey] // => "red", typed as StringThe compiler won't let you set a wrong value type, and fetching conveniently returns String.
For extra convenience, define your keys by extending magic DefaultsKeys class and adding static properties:
extensionDefaultsKeys{varusername:DefaultsKey<String?>{.init("username")}varlaunchCount:DefaultsKey<Int>{.init("launchCount", defaultValue:0)}}And use the shortcut dot syntax:
Defaults[\.username]="joe"Defaults[\.launchCount]+=1SwiftyUserDefaults 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!
Since version 4, SwiftyUserDefaults support Codable! Just conform to DefaultsSerializable in your type:
finalclassFrogCodable:Codable,DefaultsSerializable{letname:String}No implementation needed! By doing this you will get an option to specify an optional DefaultsKey:
letfrog=DefaultsKey<FrogCodable?>("frog")Additionally, you've got an array support for free:
letfroggies=DefaultsKey<[FrogCodable]?>("froggies")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:
letfrog=DefaultsKey<FrogSerializable?>("frog")Additionally, you've got an array support also for free:
letfroggies=DefaultsKey<[FrogSerializable]?>("froggies")And the last but not least, RawRepresentable support! Again, the same situation like with NSCoding and Codable:
enumBestFroggiesEnum:String,DefaultsSerializable{case Andy
case Dandy
}No implementation needed as well! By doing this you will get an option to specify an optional DefaultsKey:
letfrog=DefaultsKey<BestFroggiesEnum?>("frog")Additionally, you've got an array support also for free:
letfroggies=DefaultsKey<[BestFroggiesEnum]?>("froggies")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:
extensionUIColor: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)
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:
publicstructDefaultsKeyedArchiverBridge<T>:DefaultsBridge{publicfunc get(key:String, userDefaults:UserDefaults)->T?{
userDefaults.data(forKey: key).flatMap(NSKeyedUnarchiver.unarchiveObject)as?T}publicfunc save(key:String, value:T?, userDefaults:UserDefaults){
userDefaults.set(NSKeyedArchiver.archivedData(withRootObject: value), forKey: key)}publicfunc deserialize(_ object:Any)->T?{guardlet data = object as?Dataelse{returnnil}returnNSKeyedUnarchiver.unarchiveObject(with: data)as?T}}Bridge for default storing/retrieving array values:
publicstructDefaultsArrayBridge<T:Collection>:DefaultsBridge{publicfunc save(key:String, value:T?, userDefaults:UserDefaults){
userDefaults.set(value, forKey: key)}publicfunc get(key:String, userDefaults:UserDefaults)->T?{
userDefaults.array(forKey: key)as?T}publicfunc deserialize(_ object:Any)->T?{nil}}Now, to use these bridges in our type we simply declare it as follows:
structFrogCustomSerializable:DefaultsSerializable{staticvar_defaults:DefaultsKeyedArchiverBridge({DefaultsKeyedArchiverBridge()}staticvar_defaultsArray:DefaultsKeyedArchiverBridge{DefaultsKeyedArchiverBridge()}letname:String}Unfortunately, if you find yourself in a situation where you need a custom bridge, you'll probably need to write your own:
finalclassDefaultsFrogBridge:DefaultsBridge{func get(key:String, userDefaults:UserDefaults)->FrogCustomSerializable?{letname= 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?{guardlet name = object as?Stringelse{returnnil}returnFrogCustomSerializable(name: name)}}finalclassDefaultsFrogArrayBridge: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){letvalues= value?.map{ $0.name }
userDefaults.set(values, forKey: key)}func deserialize(_ object:Any)->[FrogCustomSerializable]?{guardlet names = object as?[String]else{returnnil}return names.map(FrogCustomSerializable.init)}}structFrogCustomSerializable:DefaultsSerializable,Equatable{staticvar_defaults:DefaultsFrogBridge{DefaultsFrogBridge()}staticvar_defaultsArray:DefaultsFrogArrayBridge{DefaultsFrogArrayBridge()}letname:String}To support existing types with different bridges, you can extend it similarly:
extensionData:DefaultsSerializable{publicstaticvar_defaultsArray:DefaultsArrayBridge<[T]>{DefaultsArrayBridge()}publicstaticvar_defaults:DefaultsDataBridge{DefaultsDataBridge()}}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.
SwiftyUserDefaults provides property wrappers for Swift 5.1! The property wrapper, @SwiftyUserDefault, provides an option to use it with key path and options: caching or observing.
Caching means that we will store the value for you and do not hit the UserDefaults for value almost never, only for the first value fetch.
Observing means we will observe, via KVO, your property so you don't have to worry if it was saved somewhere else and you use caching.
Now usage! Given keys:
extensionDefaultsKeys{varuserColorScheme:DefaultsKey<String>{.init("userColorScheme", defaultValue:"default")}varuserThemeName:DefaultsKey<String?>{.init("userThemeName")}varuserLastLoginDate:DefaultsKey<Date?>{.init("userLastLoginDate")}}You can declare a Settings struct:
structSettings{@SwiftyUserDefault(keyPath: \.userColorScheme)varuserColorScheme:String@SwiftyUserDefault(keyPath: \.userThemeName, options:.cached)varuserThemeName:String?@SwiftyUserDefault(keyPath: \.userLastLoginDate, options:[.cached,.observed])varuserLastLoginDate:Date?}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 for local DefaultsKey:
letnameKey=DefaultsKey<String>("name", defaultValue:"")Defaults.observe(key: nameKey){ update in
// here you can access `oldValue`/`newValue` and few other properties
}To observe a value for a key defined in DefaultsKeys extension:
Defaults.observe(\.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}SwiftyUserDefaults makes KeyPath dynamicMemberLookup usable in Swift 5.1!
extensionDefaultsKeys{varusername:DefaultsKey<String?>{.init("username")}varlaunchCount:DefaultsKey<Int>{.init("launchCount", defaultValue:0)}}And just use it ;-)
// Get and set user defaults easily
letusername=Defaults.username
Defaults.hotkeyEnabled =true
// Modify value types in place
Defaults.launchCount +=1Defaults.volume -=0.1Defaults.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.0Do 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.
func testExample(){letapp=XCUIApplication()
app.launchArguments =["-skipLogin","true","-loginTries","3","-lastGameTime","61.3","-nickname","sunshinejr"]
app.launch()}./script -skipLogin true -loginTries 3 -lastGameTime 61.3 -nickname sunshinejrTo reset user defaults, use removeAll method.
Defaults.removeAll()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:
varDefaults=DefaultsAdapter<DefaultsKeys>(defaults:UserDefaults(suiteName:"com.my.app")!, keyStore:.init())If you want to check if we've got a value for DefaultsKey:
lethasKey=Defaults.hasKey(\.skipLogin)Swift version >= 4.1
iOS version >= 9.0
macOS version >= 10.11
tvOS version >= 9.0
watchOS version >= 2.0
If you're using CocoaPods, just add this line to your Podfile:
pod'SwiftyUserDefaults','~> 5.0'Install by running this command in your terminal:
pod installThen import the library in all files where you use it:
import SwiftyUserDefaultsJust add to your Cartfile:
github"sunshinejr/SwiftyUserDefaults" ~> 5.0Just add to your Package.swift under dependencies:
letpackage=Package(
name:"MyPackage",
products:[...],
dependencies:[.package(url:"https://github.com/sunshinejr/SwiftyUserDefaults.git",.upToNextMajor(from:"5.0.0"))],
targets:[...])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:
If you have comments, complaints or ideas for improvements, feel free to open an issue or a pull request.
Maintainer: Łukasz Mróz
Created by: Radek Pietruszewski
SwiftyUserDefaults is available under the MIT license. See the LICENSE file for more info.
