UserDefaults under the hood šš»ā
Userās defaults database to store objects in key value format
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.
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.