Persisted Property Wrapper is a Swift library to enable extremely easy persistance of variables in the UserDefaults
database on Apple platforms.
To use Persisted Property Wrapper you simply annotate a variable as being @Persisted
. It supports the standard UserDefaults
types (Int
, String
, Bool
, Date
and more), along with RawRepresentable
enums where the RawValue
is storable in UserDefaults
, as well as any type which conforms to Codable
or NSSecureCoding
. Plus of course any Optional
wrapper of any of these types. The type-validity is checked at compile-time: attempting to use on any variables of a non-supported type will cause a compile-time error.
Stick a @Persisted
attribute on your variable.
The first argument of the initializer is the string key under which the value will be stored in UserDefaults
. If the type is non-Optional, you must also supply a defaultValue
, which will be used when there is no value stored in UserDefaults
.
For example:
@Persisted("UserSetting1", defaultValue: 42)
var someUserSetting: Int
@Persisted("UserSetting2") // defaultValue not necessary since Int? is an Optional type
var someOtherUserSetting: Int?
Want to store an enum value? If the enum has a backing type which is supported for storage in UserDefaults
, then those can also be marked as @Persisted
, and the actual value stored in UserDefaults
will be the enum's raw value. For example:
enum AppTheme: Int {
case brightRed
case vibrantOrange
case plainBlue
}
struct ThemeSettings {
// Stores the underlying integer backing the selected AppTheme
@Persisted("theme", defaultValue: .plainBlue)
var selectedTheme: AppTheme
}
Any codable type can be Persisted too; this will store in UserDefaults the JSON-encoded representation of the variable. For example:
struct AppSettings: Codable {
var welcomeMessage = "Hello world!"
var isSpecialModeEnabled = false
var launchCount = 0
@Persisted(encodedDataKey: "appSettings", defaultValue: .init())
static var current: AppSettings
}
// Example usage: this will update the value of the stored AppSettings
func appDidLaunch() {
AppSettings.current.launchCount += 1
}
Note that the argument label encodedDataKey
must be used. This is required to remove ambiguity about which storage method is used, since UserDefaults
-storable types can be Codable
too.
For example, the following two variables are stored via different mechanisms:
// Stores the integer in UserDefaults
@Persisted("storedAsInteger", defaultValue: 10)
var storedAsInteger: Int
// Store the data of a JSON-encoded representation of the value. Don't use on iOS 12!
@Persisted(encodedDataKey: "storedAsData", defaultValue: 10)
var storedAsData: Int
Note: on iOS 12, using the encodedDataKey
initializer with a value which would encode to a JSON fragment (e.g. Int
, String
, Bool
, etc) will cause a crash. This is due to a bug in the Swift runtime shipped prior to iOS 13. Using encodedDataKey
has no benefit in these cases anyway.
Any NSObject
which conforms to NSSecureCoding
can be Persisted too; this will store in UserDefaults the encoded representation of the object obtained from NSKeyedArchiver
. For example:
class CloudKitSyncManager {
@Persisted(archivedDataKey: "ckServerChangeToken")
var changeToken: CKServerChangeToken?
}
Note that the argument label archivedDataKey
must be used. As above, this is required to remove ambiguity about which storage method is used.
Note: this storage mechanism is only supported on iOS 11 and up.
By default, a @Persisted
property is stored in the UserDefaults.standard
database; to store values in a different location, pass the storage:
parameter to the property wrapper:
extension UserDefaults {
static var alternative = UserDefaults(suiteName: "alternative")!
}
@Persisted("alternativeStoredValue", storage: .alternative)
var alternativeStoredValue: Int?
After all, there are lots of examples of similar utilities on the web. For example, this post by John Sundell shows how a @UserDefaultsBacked
property wrapper can be written in a handful of lines.
However, during development of my app, I found that I really wanted to store enum values in UserDefaults
. For any enum which is backed by integer or a string, there was an obvious ideal implementation - store the enum's raw value. To provide a single API to persist both UserDefaults
-supported types as well as enum values backed by UserDefaults
-supported types proved a little tricky; adding the requirement that everything needed to also work on Optional
wrappers of any supported type, and the problem became more complex still. Once solved for my app, I thought why not package up?
- Xcode 12
- Swift 5.3
Add https://github.com/AndrewBennet/PersistedPropertyWrapper.git
as a Swift Package Dependency in Xcode.
To install via CocoaPods, add the following line to your Podfile:
pod 'PersistedPropertyWrapper', '~> 2.0'
Copy the contents of the Sources
directory into your project.
- SwiftyUserDefaults has more functionality, but you are required to define your stored properties in a specific extension.
- AppStorage: native Apple property wrapper, but tailored to (and defined in) SwiftUI, and only available in iOS 14