{"id":45271,"date":"2024-10-07T13:28:14","date_gmt":"2024-10-07T17:28:14","guid":{"rendered":"https:\/\/mjtsai.com\/blog\/?p=45271"},"modified":"2025-12-16T15:48:36","modified_gmt":"2025-12-16T20:48:36","slug":"launching-before-userdefaults-is-available","status":"publish","type":"post","link":"https:\/\/mjtsai.com\/blog\/2024\/10\/07\/launching-before-userdefaults-is-available\/","title":{"rendered":"Launching Before UserDefaults Is Available"},"content":{"rendered":"<p><a href=\"https:\/\/christianselig.com\/2024\/10\/beware-userdefaults\/\">Christian Selig<\/a> (<a href=\"https:\/\/mastodon.social\/@christianselig\/113257286544490651\">Mastodon<\/a>):<\/p>\n<blockquote cite=\"https:\/\/christianselig.com\/2024\/10\/beware-userdefaults\/\">\n<p>It seems at some point, even though <code>UserDefaults<\/code> is intended for non-sensitive information, it started getting marked as data that needs to be encrypted and cannot be accessed until the user unlocked their device. I don&rsquo;t know if it&rsquo;s because Apple found developers were storing sensitive data in there even when they shouldn&rsquo;t be, but the result is even if you just store something innocuous like what color scheme the user has set for your app, that theme cannot be accessed until the device is unlocked.<\/p>\n<p>[&#8230;]<\/p>\n<p>Again, who cares? Users <em>have<\/em> to unlock the device before launching my app, right? I thought so too! It turns out, even though Apple&rsquo;s prewarming documentation states otherwise, developers have been reporting for years <a href=\"https:\/\/stackoverflow.com\/questions\/71025205\/ios-15-prewarming-causing-appwilllaunch-method-when-prewarm-is-done\">that that&rsquo;s just wrong<\/a>, and your app can effectively be fully launched at any time, including before the device is even unlocked.<\/p>\n<p>Combining this with the previous <code>UserDefaults<\/code> change, you&rsquo;re left with the above situation where the app is launched with crucial data just completely unavailable because the device is still locked.<\/p>\n<p>[&#8230;]<\/p>\n<p>If you use Live Activities at all, the cool new API that puts activities in your Dynamic Island and Lock Screen, it seems if your app has an active Live Activity and the user reboots their device, <strong>virtually 100% of the time<\/strong> the above situation will occur where your app is launched in the background without <code>UserDefaults<\/code> being available to it.<\/p>\n<\/blockquote>\n<p><code>UserDefaults<\/code> doesn&rsquo;t actually report an error, and this can lead to incorrect behavior and data loss.<\/p>\n\n<p><a href=\"https:\/\/mastodon.social\/@christianselig\/113252110107259956\">Christian Selig<\/a>:<\/p>\n<blockquote cite=\"https:\/\/mastodon.social\/@christianselig\/113252110107259956\"><p>It&rsquo;s a really rough bug, because if you sometimes can&rsquo;t trust <code>UserDefaults<\/code>, it means you can read out data from it with the intent to modify it slightly before saving it back, only now you&rsquo;re modifying junk data and overwriting the good data &#x1FAE4;<\/p><\/blockquote>\n\n<p><a href=\"https:\/\/developer.apple.com\/forums\/thread\/15685?answerId=45849022#45849022\">Quinn<\/a> (in 2015):<\/p>\n<blockquote cite=\"https:\/\/developer.apple.com\/forums\/thread\/15685?answerId=45849022#45849022\"><p>IMO the best way around this is to avoid <code>NSUserDefaults<\/code> for stuff that you rely on in code paths that can execute in the background. Instead store those settings in your own preferences file, one whose data protection you can explicitly manage[&#8230;]<\/p><\/blockquote>\n\n<p><code>NSFileProtectionCompleteUntilFirstUserAuthentication<\/code> and prewarming don&rsquo;t apply on the Mac, but similar problems can occur there. A common cause of support issues is that the customer has entered some data or changed a setting, and the app seems to work normally for that launch, but upon relaunch all the changes are forgotten. macOS doesn&rsquo;t notify the app that the data was never saved to the <tt>.plist<\/tt> file, e.g. because of a file permissions problem or because the user tried to redirect the user defaults storage using a symlink.<\/p>\n\n<p>Previously:<\/p>\n<ul>\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>\n\n<p id=\"launching-before-userdefaults-is-available-update-2024-10-08\">Update (2024-10-08): <a href=\"https:\/\/mastodon.social\/@Gte\/113267417728962475\">Guy English<\/a>:<\/p>\n<blockquote cite=\"https:\/\/mastodon.social\/@Gte\/113267417728962475\">\n<p>This is a very nasty corner of a dead simple API that can lead to data loss for customers. UserDefaults should be as straightforward to use as possible. I&rsquo;d propose a <em>new<\/em> <code>UserDefaults<\/code> domain for &ldquo;public&rdquo; data that you&rsquo;d access via something like: <code>UserDefaults.public<\/code>.<\/p>\n<p>[&#8230;]<\/p>As more of app functionality is subsumed by the OS managing a series of plugins this sort of thing must be reliable and foolproof.\n<p>[&#8230;]<\/p>\n<p>&ldquo;Changed&rdquo; may be off the mark here too&mdash;I think those security changes were done way back in iOS 7. I suspect what&rsquo;s happening is that the issue is becoming more apparent due to the differing security contexts code is running under more frequently now. It makes sense to secure the Defaults. But it also makes sense to store non-sensitive defaults there&#8230; so it&rsquo;s a pickle currently.<\/p><\/blockquote>\n\n<p><a href=\"https:\/\/infosec.exchange\/@pasi\/113270059612937514\">Pasi Salenius<\/a>:<\/p>\n<blockquote cite=\"https:\/\/infosec.exchange\/@pasi\/113270059612937514\"><p>In addition to encrypted userdefaults being unavailable on background launches, there is something else that is not working right. I sometimes notice defaults not being available in <code>viewDidLoad<\/code>, and have received reports of that happening to my users. It usually happens after an OS update and reboot.<\/p><\/blockquote>\n\n<p><a href=\"https:\/\/mastodon.social\/@christianselig\/113271803680886188\">Christian Selig<\/a>:<\/p>\n<blockquote cite=\"https:\/\/mastodon.social\/@christianselig\/113271803680886188\"><p>Yeah that&rsquo;s part of the whole &ldquo;full launch&rdquo; business unfortunately, <code>appDidFinishLaunching<\/code> and corresponding view controllers will be fully getting built without <code>UserDefaults<\/code> being available even though the docs on prewarming state otherwise.<\/p><\/blockquote>\n\n<p><a href=\"https:\/\/christianselig.com\/2024\/10\/introducing-tiny-storage\/\">Christian Selig<\/a>:<\/p>\n<blockquote cite=\"https:\/\/christianselig.com\/2024\/10\/introducing-tiny-storage\/\">\n<p>I wanted to build something small and lightweight that would serve to fix the issues I was encountering with <code>UserDefaults<\/code> and thus <code>TinyStorage<\/code> was born! It&rsquo;s open source so you can use it in your projects too if would like.<\/p>\n<\/blockquote>","protected":false},"excerpt":{"rendered":"<p>Christian Selig (Mastodon): It seems at some point, even though UserDefaults is intended for non-sensitive information, it started getting marked as data that needs to be encrypted and cannot be accessed until the user unlocked their device. I don&rsquo;t know if it&rsquo;s because Apple found developers were storing sensitive data in there even when they [&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":"2024-10-07T17:28:18Z","apple_news_api_id":"9e5a0c55-f6f2-41ad-9a7c-3ef1245f8dc9","apple_news_api_modified_at":"2024-10-08T20:07:25Z","apple_news_api_revision":"AAAAAAAAAAAAAAAAAAAABA==","apple_news_api_share_url":"https:\/\/apple.news\/AnloMVfbyQa2afD7xJF-NyQ","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":[69,1016,547,31,2078,2586,469,2217,30,2598,2868,71],"class_list":["post-45271","post","type-post","status-publish","format-standard","hentry","category-programming-category","tag-cocoa","tag-datacide","tag-permissions","tag-ios","tag-ios-15","tag-ios-18","tag-ios7","tag-live-activities","tag-mac","tag-macos-15-sequoia","tag-nsuserdefaults","tag-programming"],"apple_news_notices":[],"_links":{"self":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/45271","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=45271"}],"version-history":[{"count":6,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/45271\/revisions"}],"predecessor-version":[{"id":45284,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/45271\/revisions\/45284"}],"wp:attachment":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/media?parent=45271"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/categories?post=45271"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/tags?post=45271"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}