{"id":50486,"date":"2025-12-17T14:55:16","date_gmt":"2025-12-17T19:55:16","guid":{"rendered":"https:\/\/mjtsai.com\/blog\/?p=50486"},"modified":"2025-12-17T14:55:16","modified_gmt":"2025-12-17T19:55:16","slug":"userdefaults-registerdefaults-footgun","status":"publish","type":"post","link":"https:\/\/mjtsai.com\/blog\/2025\/12\/17\/userdefaults-registerdefaults-footgun\/","title":{"rendered":"UserDefaults.register(defaults:) Footgun"},"content":{"rendered":"<p><a href=\"https:\/\/lapcatsoftware.com\/articles\/2025\/12\/3.html\">Jeff Johnson<\/a> (<a href=\"https:\/\/mastodon.social\/@lapcatsoftware\/115729832588653050\">Mastodon<\/a>):<\/p>\n<blockquote cite=\"https:\/\/lapcatsoftware.com\/articles\/2025\/12\/3.html\">\n<p>&ldquo;Every instance of UserDefaults shares the contents of the argument and registration domains.&rdquo; In other words, the result of calling <code>registerDefaults<\/code> on the object returned by <code>[NSUserDefaults initWithSuiteName:]<\/code> is the <em>same<\/em> as calling <code>registerDefaults<\/code> on the object returned by <code>[NSUserDefaults standardUserDefaults]<\/code>! Yet the documentation for <code>registerDefaults<\/code> does not mention this fact.<\/p>\n<p>How did this become a Link Unshortener bug? In the NSApplicationDelegate method <code>applicationWillFinishLaunching<\/code>, I call <code>[NSUserDefaults initWithSuiteName:]<\/code> and <code>registerDefaults<\/code> 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 <code>[NSUserDefaults setObject: forKey:]<\/code> on the group defaults, using <code>[NSUserDefaults objectForKey:]<\/code> from the app defaults. If the default key has never been set in the app defaults, then <code>[NSUserDefaults objectForKey:]<\/code> <em>should<\/em> return nil. Or so I thought! But at that point <code>registerDefaults<\/code> has already been called on the group defaults object, and the app defaults object shares the registration domain with the group defaults object, so <code>[NSUserDefaults objectForKey:]<\/code> returns a non-nil value, which gets saved in the group defaults.<\/p>\n<\/blockquote>\n\n<p>Previously:<\/p>\n<ul>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2025\/12\/16\/swift-configuration\/\">Swift Configuration<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2024\/10\/07\/launching-before-userdefaults-is-available\/\">Launching Before UserDefaults Is Available<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2016\/02\/22\/use-and-misuse-of-nsuserdefaults\/\">Use and Misuse of NSUserDefaults<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2014\/01\/22\/debugging-nsuserdefaults\/\">Debugging NSUserDefaults<\/a><\/li>\n<\/ul>","protected":false},"excerpt":{"rendered":"<p>Jeff Johnson (Mastodon): &ldquo;Every instance of UserDefaults shares the contents of the argument and registration domains.&rdquo; 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 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"apple_news_api_created_at":"2025-12-17T19:55:21Z","apple_news_api_id":"efc020ea-b8cc-4461-8250-a5a20e462ef4","apple_news_api_modified_at":"2025-12-17T19:55:21Z","apple_news_api_revision":"AAAAAAAAAAD\/\/\/\/\/\/\/\/\/\/w==","apple_news_api_share_url":"https:\/\/apple.news\/A78Ag6rjMRGGCUKWiDkYu9A","apple_news_coverimage":0,"apple_news_coverimage_caption":"","apple_news_is_hidden":false,"apple_news_is_paid":false,"apple_news_is_preview":false,"apple_news_is_sponsored":false,"apple_news_maturity_rating":"","apple_news_metadata":"\"\"","apple_news_pullquote":"","apple_news_pullquote_position":"","apple_news_slug":"","apple_news_sections":"\"\"","apple_news_suppress_video_url":false,"apple_news_use_image_component":false,"footnotes":""},"categories":[4],"tags":[131,69,31,2741,2238,30,2742,2868,71],"class_list":["post-50486","post","type-post","status-publish","format-standard","hentry","category-programming-category","tag-bug","tag-cocoa","tag-ios","tag-ios-26","tag-link-unshortener","tag-mac","tag-macos-tahoe-26","tag-nsuserdefaults","tag-programming"],"apple_news_notices":[],"_links":{"self":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/50486","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/comments?post=50486"}],"version-history":[{"count":1,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/50486\/revisions"}],"predecessor-version":[{"id":50487,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/50486\/revisions\/50487"}],"wp:attachment":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/media?parent=50486"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/categories?post=50486"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/tags?post=50486"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}