UserDefaults under the hood šŸ’šŸ»ā€

Userā€™s defaults database to store objects in key value format

Aaina jain
Swift India
4 min readJun 4, 2018

--

Credits: burst.shopify.com

UserDefaults is a hierarchical persistent key-value store optimized for storing user settings as per Apple documentation. It manages the storage of preference for each user.

  • Persistent: Preferences stored in UserDefaults persist across reboots and relaunches of apps.
  • Key-Value Store: UserDefaults stores Property List objects (String, Data, Number, Date, Array, and Dictionary) identified by String keys.

Preferences:

Preferences are group into domains, each of which has a name and a specific usage. There is a provision to share data between multiple targets but as of now there is no support for sharing preferences between users.

Each preference has 3 components:

  • The domain in which itā€™s stored.
  • Name
  • Value

The lifetime of a preference depends on the domain you store it in. Some domains store preferences persistently by writing them to the userā€™s defaults database. Such preferences continue to exist from one app launch to the next. Other domains store preferences in a more volatile way, preserving preference values only for the life of the corresponding user defaults object.

Search order for domains

1. The Argument Domain:

The argument domain contains values set from command- line arguments (provided if app was launched from the command line) and is identified by the NSArgumentDomain constant. Values set from the command line are automatically placed into this domain by the system. To add a value to this domain, specify the preference name on the command line (preceded with a hyphen) and follow it with the corresponding value.

Preferences set from the command line temporarily override the established values stored in the userā€™s defaults database.

If you want to add feature flag or want to disable onboarding screen for tests you can add launch arguments in the format -key plistvalue. Advantage of setting in this way is it will be available as part of UserDefaults.standard. If you set launch arguments in the format key then it will be available as part of ProcessInfo().arguments. NSArgumentDomain is automatically included in all search lists.

You can list added arguments using this command:

po UserDefaults.standard.volatileDomain(forName: UserDefaults.argumentDomain)

2. The Application Domain:

It contains app-specific preferences that are stored in the user defaults database of the current user. When you use shared UserDefaults to write preferences, those preferences are automatically placed in this domain.

UserDefaults.standard returns a shared instance of UserDefaults configured to search the current applicationā€™s search list. As you store your first property in UserDefaults it creates plist file having name in this format: bundleIdentifier.plist i.e. bundleIdentifier.plist. This .plist file will locate inside $HOME/Library/Preferences. You can find root path of data folder using po NSHomeDirectory(). So actual path of plist will be like this:

/Users/userName/Library/Developer/CoreSimulator/Devices/216A1F4F-E9D8ā€“4C9E-9F8D-FDF851AC583D/data/Containers/Data/Application/623C0FE8-FA42ā€“4864-A0B7ā€“87BCD268D146/Library/Preferences

You can get list app specific preferences using:

po UserDefaults.standard.persistentDomain(forName: bundleIndentifier)
or
po UserDefaults.standard.dictionaryPresentation()

3. The Global Domain:

It contains preferences that are applicable to all apps and is identified by the NSGlobalDomain constant. This domain is used by system frameworks to store system-wide values and should not be used by your app to store app-specific values. If you want to change the value of a preference in the global domain, write that same preference to the application domain with the new value.

Eg: AppleLanguages key to store userā€™s preferred language.

4. The Registration Domain:

This domain defines the set of default values to use if a given preference is not set explicitly in other domains. At launch time, an app can call the registerDefaults: method of NSUserDefaults to specify a default set of values for important preferences. When an app launches for the first time, most preferences have no values so retrieving them would yield undefined results. Registering a set of default values ensures that your app always has a known good set of values to operate on.NSRegistrationDomain identifies a search list entry containing all defaults set with -registerDefaults.

You can get all registered preferences using:

po UserDefaults.standard.volatileDomain(forName: UserDefaults.registrationDomain)

If you want to share data between multiple targets or extensions then you need to persist data in shared container using:

UserDefaults.standard.addSuite(named: ā€œUserDetailsā€)

It will create new property list file named UserDefaults.plist` in $HOME/Library/Preferences path.

Still you wonā€™t be able to access this suite in extension, you need to add appGroup in capability and then use appGroup name as suite name here.

Read more about sharing UserDefaults between extension on this article https://medium.com/ios-os-x-development/shared-user-defaults-in-ios-3f15cd2c9409

Removing UserDefaults:

UserDefaults domain can be removed using:

  • removeSuite(named:) It will remove a sub-searchlist added via -addSuiteNamed method
  • removePersistentDomain(forName: ) It will remove all values from the search list entry specified by ā€˜domainNameā€™.

UserDefaults can be observed using KVO for any key stored in it.

Conclusion:

Shared UserDefaults always store data in property list file inside app folder. UserDefaults can be easily mocked for tests by adding suitName and then remove in tearDown(). For disabling some features for UI tests many times we add launch arguments. Those launch arguments can be easily read using UserDefaults if added in the correct format. Domain preferences can be easily read from terminal also by defaults command. Eg.

defaults read com.ajain.userdefault
defaults read NSGlobalDomain

Thanks for reading.

For any queries reach out to me on: Linkedin or Twitter

--

--