Lazily Populating User Defaults With Default Values

Download Your Free Copy of
The Missing Manual
for Swift Development

The Guide I Wish I Had When I Started Out

Join 20,000+ Developers Learning About Swift Development

Download Your Free Copy

Most applications store small pieces of information in the defaults system. The UserDefaults class, defined in the Foundation framework, provides quick access to the defaults system through an easy to use interface. The following code snippet should look familiar.

UserDefaults.standard.set(true, forKey: "didEnableHealthKit")

The settings view of Samsara manages a sizeable list of configuration options. Most of these are stored in the defaults system. When your application launches for the first time on the user's device, the defaults system doesn't contain any settings. The user starts with a clean slate.

Samsara's Settings View

That is fine for some settings. But sometimes you want to populate an application with a set of default settings. There are several solutions to this problem.

Populate on Launch

A common approach is to populate the defaults system when the application is launched. This is easy and straightforward.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    let userDefaults = UserDefaults.standard

    if userDefaults.object(forKey: "didEnableHealthKit") == nil {
        userDefaults.set(true, forKey: "didEnableHealthKit")
    }

    return true
}

We can improve this by working with a constant instead of a string literal. We create an extension for the UserDefaults class, define a structure, Keys, and define a static constant of type String. We declare the initializer of the Keys structure private to prevent any instances of the structure from being created.

extension UserDefaults {

    struct Keys {
        private init() {}

        static let DidEnableHealthKit = "didEnableHealthKit"
    }

}

The result looks much better if you ask me.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    let userDefaults = UserDefaults.standard

    if userDefaults.object(forKey: UserDefaults.Keys.DidEnableHealthKit) == nil {
        userDefaults.set(true, forKey: UserDefaults.Keys.DidEnableHealthKit)
    }

    return true
}

Lazily Populate on Access

There is a solution that is even better. I often create an extension, or a category in Objective-C, for the UserDefaults class that provides quick access to settings that are frequently used in the application. It looks something like this.

extension UserDefaults {

    class var didEnableHealthKit: Bool {
        let userDefaults = UserDefaults.standard
        return userDefaults.bool(forKey: UserDefaults.Keys.DidEnableHealthKit)
    }

    class func set(didEnableHealthKit: Bool) {
        let userDefaults = UserDefaults.standard
        userDefaults.set(true, forKey: UserDefaults.Keys.DidEnableHealthKit)
    }

}

This results in a clean, convenient interface.

UserDefaults.didEnableHealthKit
UserDefaults.set(didEnableHealthKit: true)

This pattern also lets us lazily populate the defaults system with default values. Take a look at the updated implementation of the extension for the UserDefaults class.

extension UserDefaults {

    class var didEnableHealthKit: Bool {
        let userDefaults = UserDefaults.standard

        if userDefaults.object(forKey: UserDefaults.Keys.DidEnableHealthKit) == nil {
            UserDefaults.set(didEnableHealthKit: true)
        }

        return userDefaults.bool(forKey: UserDefaults.Keys.DidEnableHealthKit)
    }

    ...

}

How does this work? When the application accesses the value of the key UserDefaults.Keys.DidEnableHealthKit, it asks the default system for the object associated with that key. Notice that we ask for the object by invoking object(forKey:). That is important because bool(forKey:) doesn't return an optional, it always returns true or false. Even if the key UserDefaults.Keys.DidEnableHealthKit is not present in the defaults system, bool(forKey:) returns false. The object(forKey:) method returns nil, which tells us that the key doesn't exist in the defaults system.

If the key doesn't exist, we set a default value by invoking the convenience method we implemented earlier. That's it. The implementation is cleaner and not cluttering the application's launch sequence. Even though populating the defaults system on launch is not going to have a noticeable impact on performance, I prefer to do as little as possible during the application't launch sequence.

More Options

You can go one step further and load the default values from a property list or a JSON file. Little improvements like this can significantly improve your codebase. Swift gives you the tools. It is up to you to use them.

Download Your Free Copy of
The Missing Manual
for Swift Development

The Guide I Wish I Had When I Started Out

Join 20,000+ Developers Learning About Swift Development

Download Your Free Copy