UserDefaults Issue After iPhone Restart

It seems that the first time I open my app after the phone is restarted, the app does not properly access UserDefaults.

When my app is launched, I pull relevant user settings / preferences from UserDefaults. I recently noticed (after some users reached out about issues) that the first time the app is opened after a restart, the app appears as though all user settings / preferences have been erased.

If the app is force quit and reopened, the user data seems to come back normally. If the user takes action in the app before force quitting, though, UserDefaults will be written to with these new, empty values (essentially erasing what used to be in UserDefaults).

This makes it seem as though right after the phone is restarted, the app is unable to pull data from UserDefaults for some reason. Has anyone else seen this issue?

It could also be due to the lifecycle of my app and how I access UserDefaults, so if others don't have this issue it would be great to hear how you handle this.

Replies

I use a set of general functions that get values from the defaults, and return a fallback value if the defaults couldn't be read. It's helpful to abstract this out so you can change the way you store such data in future and only need to change these functions, not everywhere you currently access defaults.

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

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

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

func defaultsGetString(forKey key: String, withFallback fallback: String) -> String {
	guard let value = UserDefaults(suiteName: kAppGroup)?.object(forKey: key) else { return fallback }
	return value as? String ?? fallback
}

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

And I use them like this:

func defaultsGetSettingsEvents() -> Int {
	return defaultsGetInt(forKey: kSettingsEvents, withFallback: 10)
}

func defaultsUpdateSettingsEvents(_ value: Int) {
	defaultsSet(value as Int, forKey: kSettingsEvents)
}

So, when the app launches I get the events and ask for a fallback of 10 if it couldn't get that value. When I update that value, it's stored and defaults are synchronised.

In your situation where the defaults aren't accessed correctly initially, this would appear as though the defaults have been reset - because you'd be returning fallback values.

Are you sure you're accessing them in every place from your app group? Are you using Strings for the keys - perhaps there's a typo? Use constants instead: let kSettingsEvents: String = "settingsEvents".

It sounds like you can reproduce this pretty reliably. If so, I have a test that should be revelatory:

  1. Completely gut your app such that the only remaining code is something that reads one of your user defaults and logs it.

  2. Repeat your test with that.

Does it have the same problem?

Now take your remaining code and put it into a new app, one created from Xcode’s iOS > App template template. Does it have the same problem?

My experience is that UserDefaults doesn’t drop preferences at random, but folks can run into problems due to the combination of data protection and background execution. If your app runs in the background at a time when the files backing UserDefaults aren’t available due to data protection, you can see issues like this.

This is particularly tricky if you use the com.apple.developer.default-data-protection entitlement to change your container’s default data protection. IMO that’s an entitlement you should never use.

It even more tricky if you once did this and then decided it was a bad idea and stopped. At that point you have some users where your app’s container has this non-default protection and some where it doesn’t.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"