How to Persist App Data (UserDefaults)?

I use UserDefaults to store a variety of user data / preferences. Recently, I have started getting somewhat frequent complaints from users that their settings have been reset.

There doesn't appear to be a rhyme or reason to the issue (some users have all of their data reset just once, while others are seeing that one of their settings resets very frequently).

I haven't been able to pinpoint what the root cause is, but I figure that it must have something to do with UserDefaults (either all of it or only certain keys) somehow getting erased.

As I figure out the root cause, I figure that a good solution in the meantime is to backup UserDefaults in some way (e.g. perhaps to iCloud?).

Is there a standard / best-practice way of doing this?

Replies

I've not experience that with UserDefaults. I wouldn't give up on finding that issue. But anything I want protected in cases where a user might lose or change their device, I use CoreData with NSPersistentCloudKitContainer. Which is easy to remember if you watch this video as this Nick Gillet says it 1.5 times per minute.

  • I didn't mean to write "this" in front of his name.

  • Thanks for the info! I'll definitely check out that video.

Add a Comment

I wouldn't over-complicate it just yet. Add a print statement every time you write to or read from the defaults. It might be that you're wiping out a value accidentally.

I was getting a similar issue because I had spelled a key wrongly. I was writing "events" and reading "evetns" in one place. Changed to using constants (let kEvents: String = "events" etc.), and everything is fine; no opportunity for typos.

I also created set and get functions for the UserDefaults:

func settingsSet(_ value: Any, forKey key: String) {
	UserDefaults(suiteName: kAppGroup)?.set(value, forKey: key)
	UserDefaults(suiteName: kAppGroup)?.synchronize()
}

func settingsGetBool(forKey key: String, withFallback fallback: Bool) -> Bool {
	guard let value = UserDefaults(suiteName: kAppGroup)?.object(forKey: key) else { return fallback }
	return value as? Bool ?? fallback
}
// ... repeat for Int, String, Date...

// Then, each individual setting has functions like this:
func settingsSetUseImages(_ value: Bool) {
	settingsSet(value as Bool, forKey: kSettingsUseImages)
}

func settingsGetUseImages() -> Bool {
	return settingsGetBool(forKey: kSettingsUseImages, withFallback: true)
}

// Usage:
settingsSetUseImages(true)  // Set to true
let useImages = settingsGetUseImages()  // Get current value, and if not set, return `fallback` of true

You can add a print statement to the defaultsSet function to write out what value is being set for which key.

By abstracting that out you can also replace the means of storing settings values later on without having to change any of the instances of where you're actually updating/reading those values.

Hope this helps. Might be overkill for your issue, but it helped me :)

  • I appreciate the thoughts / example! It happens pretty rarely, so it doesn't seem like it's an errant key. But still worth looking into.

Add a Comment