Give your simulator superpowers

RocketSim: An Essential Developer Tool
as recommended by Apple

User Defaults reading and writing in Swift

User Defaults are the go-to solution for Swift applications to store preferences that persist across launches of your app. It’s a key-value store backed by a property list (plist) file. Due to this type of backing store, you need to be aware of the supported storage types.

There are a few best practices when working with User Defaults. I can also recommend specific solutions based on implementation experience from using it in tens of apps. Let’s dive in!

What are User Defaults?

Apps commonly use User Defaults to store users’ preferences. You can store preferences like the user’s favorite stocks or save specific user states like “user has seen the onboarding.”

The code to store preferences like these could look as follows:

UserDefaults.standard.set(true, forKey: "has-seen-onboarding")
UserDefaults.standard.set(["AAPL", "TSLA"], forKey: "favorite-stocks")

print(UserDefaults.standard.bool(forKey: "has-seen-onboarding")) 
// Prints: true
print(UserDefaults.standard.array(forKey: "favorite-stocks")) 
// Prints: ["AAPL", "TSLA"]

In this case, we’re using the standard user defaults container. In most cases, this will be sufficient. However, you might want to consider using group user defaults.

Sharing User Defaults with other apps and extensions

Using so-called app groups, you can share the User Defaults container with other apps and extensions. I highly recommend using this technique for any app from the start. Even though there might not be a need to share preferences right now, you’ll thank yourself later if you add extensions that need to read or write preferences from the main app.

To configure App Groups you need to add a new capability to your project’s settings:

You can start sharing User Defaults with other apps and extensions by adding the App Groups capability.
You can start sharing User Defaults with other apps and extensions by adding the App Groups capability.

You can find detailed instructions inside Apple’s documentation. Once configured, you can create a new instance using the group identifier:

extension UserDefaults {
    static let group = UserDefaults(suiteName: "group.your.identifier")
}

You can now access the shared group container anywhere by making use of the static property:

UserDefaults.group.set(["AAPL", "TSLA"], forKey: "favorite-stocks")

Any app or extension configured with the same app group will now be able to read and write the favorite stocks. I’m using this technique inside Stock Analyzer to populate widgets based on favorite stocks configured in the main application.

The types of data User Defaults support

Property lists must support the objects you store inside User Defaults. You’ll run into the following error as soon as you write an unsupported object:

*** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘Attempt to insert non-property list object UserDefaults.Stock(symbol: “AAPL”) for key last-opened-stock’

In this case, I tried to store an encodable object:

struct Stock: Decodable {
    let symbol: String
}

UserDefaults.group.set(Stock(symbol: "AAPL"), forKey: "last-opened-stock")

Anytime you run into an exception like this, you must convert the data before storing it. You can use a JSONEncoder to encode the instance to data and decode it when you read the value.

User Defaults support the following types:

  • Data
  • Strings
  • Numbers (NSNumber)
  • Dates
  • Arrays
  • Dictionaries
  • Booleans

If your type is not on this list, you need to find a way to convert it to any of the supported types.

Responding to changes

While you can use the didChangeNotification to observe for changes, I recommend looking into managed solutions like this User Defaults Property Wrapper.

Monitoring UserDefaults changes

While working on features that interact with User Defaults, you want to have a way to monitor changes in real-time. To solve this problem, I built a User Defaults Editor into RocketSim, allowing you to edit and monitor key-value pairs in real time.

For example, I’m working on a tooltip in the following video that shows inside the WeTransfer application. The tooltip should only be showing once per user, and I want to ensure the User Defaults key hasShownUploadFilesTooltip updates accordingly. You can open the editor by clicking the Perform button and selecting the User Defaults plist file.

RocketSim’s User Defaults Editor allows you to edit and view User Defaults values in real time.

The editor monitors values constantly and flashes a blue background color when the value changes. At the same time, I can reset the value using the switch and restart the application using RocketSim to see if the tooltip shows again.

You can imagine this drastically speeds up the workflow of testing implementations that rely on UserDefaults. What’s best is that you can get started for free and test the editor with any standard suites by installing RocketSim from the Mac App Store.

Overriding UserDefaults for debugging purposes

While using RocketSim helps you to change and debug optimally, you might want to use scheme settings to override User Defaults during debugging. For that, I wrote a dedicated post: Overriding UserDefaults for improved productivity.

Alternatives considered

User Defaults are a great solution in most cases, but you might want to explore other solutions in case you’re storing sensitive data or when you want to access data across devices.

Keychain for security

User Defaults are not secure enough to store sensitive data. User credentials, API keys, or other sensitive data should be stored inside the keychain instead.

CloudKit for cross platform

Consider using the NSUbiquitousKeyValueStore in case you want the preferences to be accessible from other Apple devices with your app installed. It’s a similar key-value store but uses iCloud as a backing store.

Conclusion

You can store preferences using User Defaults and capture state across app launches. App Groups are great for sharing preferences with other apps and extensions, and you need to keep an eye on the types of data you can store. By monitoring the backing store, you’ll ensure no unexpected stored data. When you need to access data across devices or when you want to store sensitive data, it’s better to look at alternatives.

If you like to improve your Swift knowledge, even more, check out the Swift category page. Feel free to contact me or tweet to me on Twitter if you have any additional tips or feedback.

Thanks!

 

Stay updated with the latest in Swift

The 2nd largest newsletter in the Apple development community with 18,327 developers.


Featured SwiftLee Jobs

Find your next Swift career step at world-class companies with impressive apps by joining the SwiftLee Talent Collective. I'll match engineers in my collective with exciting app development companies. SwiftLee Jobs