NSUserDefaults values lost on background launch

There have been many threads and bugs about this, but it's happening to me again in ios 9. I have an app that launches in the background in response to NSURLSession tasks and content-available pushes. Reproducibly, if I reboot my phone and wait for a background launch of my app to happen, then when I open the app I find that [[NSUserDefaults standardUserDefaults] dictionaryRepresentation] contains all the system values, e.g. AppleITunesStoreItemKinds, etc. but does not contain any of the values I have set. If I force-quit and relaunch the app all of my values come back. Is there any way to avoid it caching the "empty" standardUserDefaults from before the phone is unlocked, or at least to determine when they are messed up and fix them without having to force-quit the app?


Thanks,

eric


previous discussions:

https://devforums.apple.com/thread/212999

http://www.openradar.me/16761393

Replies

The problem here is that NSUserDefaults is ultimately backed by a file in your app’s container and your app’s container is subject to data protection. If you do nothing special then, on iOS 7 and later, your container uses

NSFileProtectionCompleteUntilFirstUserAuthentication
, a value that’s inherited by the NSUserDefaults backing store, and so you can’t access it prior to first unlock.

IMO the best way around this is to avoid NSUserDefaults 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 (in this case that means ‘set to

NSFileProtectionNone
’).

There are two problems with NSUserDefaults in a data protection context:

  • Its a fully abstract API: the presence and location of its backing store is not considered part of that API, so you can’t explicitly manage its data protection.

    Note On recent versions of OS X NSUserDefaults is managed by a daemon and folks who try to manipulate its backing store directly have run into problems. It’s easy to imagine the same sort of thing coming to iOS at some point.

  • Even if changing the data protection were possible, NSUserDefaults has no mechanism to classify data based on the context in which you’re using it; it’s an ‘all or nothing’ API. In your case you don’t want to remove protection from all of your user defaults, just those that you need to access in the background before first unlock.

Finally, if any of this data is truly sensitive, you should put it in the keychain. Notably, the keychain does have the ability to set data protection on an item-by-item basis.

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Quinn,


We just run into a similar issue whilst adding background fetch to our app. Until now, our default Data Protection (as specified in the App ID) has been "Complete". I was wondering if changing this to "until first unlock" would have any affect on existing installs of the app as far as NSUserDefaults is concerned once the user updates to the newer version of the app, or will the existing backing store remain at "Complete" for ever?

Thanks for the attention Quinn. The problem isn't that I need access to NSUserDefaults before the first unlock, it's that if my app is launched before the first unlock then after I do actually unlock the phone the NSUserDefaults are stuck cached in the poisoned, empty state as if it had never been unlocked. I guess I should file a radar? It seems like there's been several of them over the years so I want to make sure there's not something else I'm supposed to be doing: http://www.openradar.me/16761393

I was wondering if changing this to "until first unlock" would have any affect on existing installs of the app as far as NSUserDefaults is concerned once the user updates to the newer version of the app, or will the existing backing store remain at "Complete" for ever?

AFAIK changing the default file protection setting for the app does not change the settings of existing containers that are updated to the new version.

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hmm, So the NSUserDefaults of existing users are going to be stuck at "Complete" forever.😟


Would using [NSUserDefault resetStandardUserDefaults] cause the defaults file to be recreated with the new protection setting? If so, I can always copy all the defaults I care about, reset the defaults and then re-add them.


I suspect the answer is no though.

The file is in your app's container directory, so you should (in theory) be able to modify its data protection yourself. Use NSFilemanager to set its

NSFileProtectionKey
attribute. Of course, as Quinn said, there's the potential for all sorts of fun if iOS ever implements the joy that is OS X's cfprefsd (which I find myself killing at least once a week after hand-modifying some random preferences file), but at least doing so would give you a way to fix the problem for existing users for now.


Alternatively, you could move that data out of the preferences file, and then use the

isProtectedDataAvailable
method on
UIApplication
to determine whether or not to read your app's preferences.

Hi Quinn,


A while ago you said that you thought iOS apps are never launched before first unlock in the following thread-


https://devforums.apple.com/message/956199#956199


"IIRC we don't auto launch apps until after the first unlock so I don't think this will be an issue in practice."


It seems per your comment above that now iOS apps can be launched before first unlock. Can you describe a circumstance in which an app will be launched before first unlock in iOS 9?


I have tested a number of background launch scenarios - nsurlsession background download, voip, background fetch, significant location change, remote push. None of them appear to launch the app before first unlock.


Your help is appreciated.


Thanks,

Misha

Dear Quinn

I'm experiencing the very same problem like Eric,

This is the scenario I experience:

1. Phone battery is off - it is shut down

2. Connect the phone to power

3. Unlock the phone and lock again.

4. After a while - a beacon detection event makes my App launch in the background.


Like Eric mentioned - in this Launch, I get NULL for my saved custom NSUserDefaults values that I need for the launch.

If I will kill the App and reopen it - values will be read correctly from NSUserDefaults.


It is for sure nothing to do with first unlock after reboot, because I unlocked it, locked it again and only after that - App launched in background not being able to get the good NSUserDefaults values.


I hear from you that it will not happen with file righ?

Eric - how did you solve it?


Thanks

A while ago you said that you thought iOS apps are never launched before first unlock in the following thread-

Yeah, the ‘auto launch before first unlock’ story is a complex one, where the specifics have changed over time, and I don’t have all the details on it. AFAIK it is currently (iOS 9) possible for apps to be auto launched before unlock under these circumstances:

  • VoIP

  • region monitoring

However, I’m not the expert on this. If you need definitive answers, please open a DTS tech support incident and one of my colleagues can help you out.

Regardless, my advice from my earlier (Aug 27, 2015) post still stands: NSUserDefaults is not the right API to use if you need control over the accessibility of your data in the background.

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

DTS will close for the winter holidays at the end of business on Wed, 23 Dec 2015 and re-open on Mon, 4 Jan 2016.

Ok thanks very much Quinn.


Misha

What error will be received if an App is blocked access to NSUserDefaults because the data is subject to data protection?

What I see is result values are 0 which, unfortunately can be a valid value.


Is there an API call to determine if NSUserDefaults are accessible?

NSUserDefaults does not have any way to communicate this error back to you. For example, for the central NSUserDefaults method,

-objectForKey:
, a result of nil means that the value is unavailable, but there’s no way to distinguish between this key is not present and this value can’t be fetched because the user defaults are offline.

Which brings me back to the advice from my first response on this thread: if your app accesses critical data while in the background, store that in your own file or, if the data is sensitive, in the keychain. That gives you explicit control over the data protection class used for your data.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I am having a similar problem with iOS 10 and 11. I don't need to access NSUserDefaults from the background, but after a few months of testing background execution, I have found that very rarely I am unable to access NSUserDefaults from applicationDidBecomeActive just after applicationProtectedDataDidBecomeAvailable was called. It seems to be a timing issue, as though NSUserDefaults is occasionally not yet unencrypted and available by the time applicationDidBecomeActive has started executing.

I have encountered this issue as well, I am able to reproduce it on iOS 11.1.1.


- (void)applicationProtectedDataDidBecomeAvailable:(UIApplication *)application
{
    VMPLogVerbose(@"applicationProtectedDataDidBecomeAvailable");
    [NSUserDefaults resetStandardUserDefaults];/
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [NSUserDefaults resetStandardUserDefaults];/
        [self launchApplication:nil];
    });
}

Even if Calling resetStandardUserDefaults multiple times, it does not help. it sounds iOS issue.

Did you ever find a way to deal with this problem? Does simply waiting get you access to NSUserDefaults?


We have seen in our testing that making the app inactive (i.e., hit the home button) and waiting (the one time this happened, the device was locked and then unlocked), then NSUserDefaults becomes available again after the app restarted in the background. This makes me think that the problem is either a timing issue (we just need to wait a little longer...) or the decryption process on NSUserDefaults just failed, and we need to go through the process again to get to that data. If it is a timing problem, then I can build a wait into my launch, like you did, and deal with the problem that way. But if the problem won't go away during the process of foregrounding, then I have no recourse but to make my app crash, so the user can restart it. I really don't want to make my app crash on purpose!!!