Wednesday, December 17, 2025

UserDefaults.register(defaults:) Footgun

Jeff Johnson (Mastodon):

“Every instance of UserDefaults shares the contents of the argument and registration domains.” In other words, the result of calling registerDefaults on the object returned by [NSUserDefaults initWithSuiteName:] is the same as calling registerDefaults on the object returned by [NSUserDefaults standardUserDefaults]! Yet the documentation for registerDefaults does not mention this fact.

How did this become a Link Unshortener bug? In the NSApplicationDelegate method applicationWillFinishLaunching, I call [NSUserDefaults initWithSuiteName:] and registerDefaults to register the default values of Link Unshortener settings. Then I check whether the app container settings need to be migrated. If migration is necessary, then I call [NSUserDefaults setObject: forKey:] on the group defaults, using [NSUserDefaults objectForKey:] from the app defaults. If the default key has never been set in the app defaults, then [NSUserDefaults objectForKey:] should return nil. Or so I thought! But at that point registerDefaults has already been called on the group defaults object, and the app defaults object shares the registration domain with the group defaults object, so [NSUserDefaults objectForKey:] returns a non-nil value, which gets saved in the group defaults.

Previously:

3 Comments RSS · Twitter · Mastodon


It's documented:

> This method assigns the key-value pairs you provide to the registration domain, which is typically the last domain in the search list. The registration domain is volatile, so you must register the set of default values each time your app launches.

https://developer.apple.com/documentation/foundation/userdefaults/register(defaults:)



Léo, it looks like you didn't read the blog post, which in the second paragraph links to the same documentation as your first comment.

And the issue isn't that the registration domain is volatile; of course we knew that.

Leave a Comment