NSUserDefaults losing its keys & values when phone is rebooted but not unlocked

After a while, Apple recognised this as an official bug. So we are only left with different workarounds until it's solved:

  1. If you need the data while executing BEFORE the phone has been unlocked use one of the following options and set the NSPersistentStoreFileProtectionKey = NSFileProtectionNone option:

    • Save data using Core Data. (If you need to access the DB in background when the phone wasn't unlocked yet AND you don't have sensible information in it, you can add to the options Array the following option: NSPersistentStoreFileProtectionKey = NSFileProtectionNone)
    • Use the Keychain.
    • Use a .plist file.
    • Use custom made files: (e.g.: .txt with a specific format).
    • Any other way you might find comfortable for storing data.

    Choose yours ;)

  2. If you don't need or don't care about the data before the phone has been unlocked you can use this approach (Thanks @maxf):

    Register to the applicationProtectedDataDidBecomeAvailable: notification and execute the following line of code inside the callback [NSUserDefaults resetStandardUserDefaults]

    This will make you NSUserDefault reload right after your phone has been granted permission to access protected data, helping you to avoid this issue entirely.

Thanks all for the help!


We also experienced this problem when using significant location change, on devices with pass code enabled. The app launches on BG before the user even unlock the pass code, and UserDefaults has nothing.

I think it is better to terminate the app before the synchronize occurs, because the reasons below:

  • UserDefaults' synchronize should not be executed once after UserDefaults cleared by this bug.
  • we can't strictly control the call of synchronize because we use many 3rd party libraries.
  • the app will not do any good if UserDefaults can't be loaded (even before the user passes pass code lock).

So here's our (a bit weird) workaround. The app kills itself immediately when the situation (app state = BG, UserDefaults is cleared, iOS >= 7) detected.

It should not violate UX standard, because terminating app on background will not be even noticed by the user. (And also it occurs before the user even passes the pass code validation)

#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v)  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)

+ (void)crashIfUserDefaultsIsInBadState
{
    if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0")
        && [UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
        if ([[NSUserDefaults standardUserDefaults] objectForKey:@"firstBootDate"]) {
            NSLog(@"------- UserDefaults is healthy now.");
        } else {
            NSLog(@"----< WARNING >--- this app will terminate itself now, because UserDefaults is in bad state and not recoverable.");
            exit(0);
        }
    }
    [[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:@"firstBootDate"];
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [self.class crashIfUserDefaultsIsInBadState]; // need to put this on the FIRST LINE of didFinishLaunchingWithOptions

    ....
}

I was having a very similar issue. Background the application. Use other memory heavy applications till my application gets jettisoned from memory. (You can observe this event if you have your device plugged and xcode running the build. Xcode will tell you "application was terminated due to memory pressure). From here if your application is registered for background fetch events, it will wake up at some point and get relaunched but into the background. At this point if your device is locked, your NSUserDefaults will be null.

After debugging this case for days, I realized it wasn't that NSUserDefaults was being corrupted or nilled out, it was that the application has no access to it due to device lock. You can actually observe this behavior if you manually try to download the application contents via xcode organizer, you'll notice that your plist which stores the NSUserDefaults settings is no present if your device remains locked.

Ok so NSUserDefaults is not accessible if application is launched into the background while device is locked. Not a big deal but the worst part of this is, once the application is launched into the background it stays in memory. At this point IF the user then unlocks the device and launches the application into the foreground, you STILL do not have anything inside NSUserDefaults. This is because once the application has loaded NSUserDefaults into memory (which is null), it doesn't know to reload it once device becomes unlocked. synchronize does nothing in this case. What I found that solved my problem was calling

[NSUserDefaults resetStandardUserDefaults] inside the applicationProtectedDataDidBecomeAvailable method.

Hope this helps someone. This information could have saved me many many hours of grief.