What are the defaults for UserDefaults?

1328 words • 6 minute read.

So what are UserDefaults?

A bald-bearded man wearing a checked shirt and glasses is sat thinking at a desk

For anyone that doesn’t know, UserDefaults provides a set of APIs that allow access to a user’s defaults database, as Apple calls it. It’s built into Foundation , which is the base framework which iOS, macOS, watchOS and all the other Apple frameworks are built upon, so UserDefaults can be accessed from anywhere without any additional import statements.

UserDefaults (as its in the name) are a light easy way of storing a users default app state across app launches, such as:

  • Toggle states in settings screens.
  • Theme state; either light or dark mode.
  • Sound preferences; on or off.
  • The last time a user logged in or launched the app.

We can also use UserDefaults for storing non-user facing things such as setting feature flags (whether a user can see a feature of not). Whether onboarding has been completed, which tab the user was on when they were last in the app. The list is endless…

So what shouldn’t we be storing in UserDefaults?

Large Amounts of Data

We shouldn’t be storing large amounts of data in UserDefaults, the reason for this is that the defaults database is loaded into your apps memory on start up, this optimises it for fast read and write times. By storing large amounts of data in UserDefaults, you could potentially be slowing the app down for your user.

Structured Data

UserDefaults has a limited data structure, with storage options restricted to key-value pairs. If you’re storing large amounts of structured data, especially objects, it’s better to use a more appropriate option like SwiftData or CoreData, depending on your specific needs. These solutions are designed to handle structured data more efficiently.

Sensitive information

We shouldn’t be storing sensitive information such as usernames, passwords or access keys. These should be stored in Keychain or encrypted and stored in CoreData, which again is more appropriate for this particular use case.

With all that in mind, lets take a look…

Basic UserDefaults Access

Lets say we have a toggle state and we want to store this value in user defaults:

var toggleState: Bool = true

We would use:

UserDefaults.standard.set(toggleState, forKey: "toggleState")

Breaking down the above, we have our boolean property toggleState which is set to true . We access UserDefaults by using UserDefaults.standard .

The first part UserDefaults being the main class.

The second part standard is whats known as a singleton, its a shared instance of UserDefaults that is used across your app, so we are always accessing the same database.

The third part set(toggleState, forKey: "toggleState") is the function which is storing our toggleState value into UserDefaults. It takes two arguments, the first being the property we are storing, and the second being the key we are storing it against, this is always a String .

Now to fetch the toggle state back out of UserDefaults we can do something like this:

if let state = UserDefaults.standard.value(forKey: "toggleState") as? Bool {
    toggleState = state
}

The first part you have seen above, the new part is value(forKey: "toggleState") . What this function does is it fetches our stored property out of UserDefaults, using the key “toggleState” , and returns it as type Any , so we then have to optionally cast it to a boolean using as? Bool .

We can then set our state, using the property we have fetched from the UserDefaults database.

There are other functions for fetching data from UserDefaults, one of then being:

func bool(forKey defaultName: String) -> Bool

So in the above instance you could have done:

var toggleState = UserDefaults.standard.bool(forKey: "toggleState")

Using this methods for fetching booleans is simpler because the value that is returned isn’t optional, meaning it will never be nil. If there is no value found in the UserDefaults database then it simply returns false. There are other functions specifically for fetching strings and integers plus many other types of properties, my suggestions would be to go and check the docs, to familiarise your self with the rest.

So how can we make it better?

Custom Wrapper for Safer Access

We could write something like this:

struct UserDefaultsStore {

    static func getString(for storageKey: DefaultsKey) -> String? {
        return UserDefaults.standard.string(forKey: storageKey.key)
    }

    static func setString(_ date: String?, for storageKey: DefaultsKey) {
        UserDefaults.standard.set(date, forKey: storageKey.key)
    }
}

extension UserDefaultsStore {

    enum DefaultsKey: String {

        case someString = "com.myProject.someString"

        var key: String {
            return rawValue
        }
    }
}

Here we have created a wrapper around the UserDefaults API for getting and setting a String property. so to use it we can simply do:

/// To set a string
UserDefaultsStore.setString("Hello World", for: .someString)

/// To get a string
let fetchedString = UserDefaultsStore.getString(for: .someString)

Now before you say it, yes I agree, its not that much different from interacting with the APIs directly, but it does remove the need to type .standard, and because we are using an enumeration for our UserDefaults keys, there’s practically no chance that we can type the wrong key by mistyping a raw string, so its safer in that sense.

Swifty UserDefaults Access with Extensions

Finally, Iets look at another more Swifty way we could use user defaults.

extension UserDefaults {
    
    private enum Keys: String {
        case toggleState = "com.myProject.toggleState"
        
        var value: String {
            return rawValue
        }
    }
    
    static var toggleState: Bool {
        get {
            UserDefaults.standard.bool(forKey: Keys.toggleState.value)
        }
        set {
            UserDefaults.standard.set(newValue, forKey: Keys.toggleState.value)
        }
    }
}

What we have done here is that we have added an extension on the UserDefaults class, adding an enumeration to hold our key properties, and a static property with a custom getter and setter. So to use it we can call:

UserDefaults.toggleState = true

or to get a property we can call:

var toggleState = UserDefaults.toggleState

This is by far my favourite way to fetch and store values from UserDefaults, because its so clean and abstracts all the logic away into the extension, making fetching and storing of properties feel more natural.

Conclusion

We’ve covered what UserDefaults are, what they’re best used for, and three different approaches to accessing and using the APIs. Each method has its strengths, but these are by no means the only ways to use them, the possibilities are endless (within reason), and it all depends on the needs and requirements of your app.

Thanks for stopping by 🙂.