3

I use NSUserDefaults to persist approximately 100 key/value pairs on the user's iOS device. Each pair is just a string key and boolean value. This works fine almost all the time. Lately, a few users mentioned their app being 'reset'. Specifically, their app was not correctly reading data from NSUserDefaults. I'm trying to understand how this can happen.

A few things to note:

  • I call synchronize after every update to the user defaults
  • I don't have any code that clears individual entries or the entire defaults
  • The defaults are read in application:didFinishLaunchingWithOptions:
  • The defaults are not read when the app moves from the background to the foreground

I found some interesting comments in this Loom.com blog post. Sounds like NSUserDefaults backing plist might be inaccessible when the app is restarted in the background. I'm not sure if backgrounded apps are restarted if they crash while in the background. However, I'm curious because my app is indeed crashing in the background according to my crash reporting service. Also, this crash happens immediately after receiving a memory warning.

Is it possible for the app to fail reading the user defaults when restarted in the background after a crash (while in the background)?

Any advice on how to diagnose this issue is greatly appreciated!

Edit - more info: sounds like the CoreLocation framework can cause apps to restart in the background after crashing in the background. My app includes a few 3rd party advertising and analytics SDKs. In fact, this problem started to show up after the addition of one particular SDK which could use CoreLocation.

SundayMonday
  • 19,147
  • 29
  • 100
  • 154
  • 1
    Is the keys/values gone forever ? – KudoCC Aug 10 '14 at 09:50
  • 1
    Does the keys that are being reset have common names? maybe a 3rd party overrides this keys? – Tomer Even Aug 10 '14 at 12:57
  • 1
    It seems that many people have the same issue. Check the [link](http://stackoverflow.com/questions/20269116/nsuserdefaults-loosing-its-keys-values-when-phone-is-rebooted-but-not-unlocked), hope help. – KudoCC Aug 10 '14 at 13:05
  • " Also, this crash happens immediately after receiving a memory warning." This means that your application did not respond to the memory warning correctly. – quellish Aug 14 '14 at 23:46
  • Does your app ever get launched in the background by a location change (or push notification)? If you have 'encryption at rest' enabled your app could be launched in the background (presumably while the phone is locked) and the NSUserDefaults are inaccessible. You can query this via [[UIApplication sharedApplication] isProtectedDataAvailable] if that returns NO you would not have access to protected data(or keychain) – drunknbass Jan 08 '15 at 00:27

3 Answers3

5

iOS does some complicated stuff to (almost) seamlessly encrypt data written to the disk, so this kind of bug is definitely possible. Perhaps the file cannot be decrypted for some reason and is deleted instead, reverting NSUserDefaults.

I don't know that is the cause but it seems likely to me.

Also, beware NSUserDefaults saves data to <Application_Home>/Library which is not a safe location. It is intended for "files your application downloads or generates and can recreate as needed".

Perhaps a better location to store your data is <Application_Home>/Documents, which is for data that "cannot be recreated by your app". If your user defaults are important enough for this to be a problem, then it classifies as "user generated content" and should therefore be stored in the Documents folder.

So, I suggest dropping NSUserDefaults since it does not meet your needs, and save the data by writing an NSDictionary to the Documents folder, using either NSCoding or Binary Plist (make sure you set it to NSPropertyListBinaryFormat_v1_0, as that is not the default and should be used on slow flash memory like iOS devices have).

Apple has good documentation and sample code for NSCoding and Plist serialization:

https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Archiving/Articles/creating.html#//apple_ref/doc/uid/20000949-BABGBHCA

https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/PropertyLists/SerializePlist/SerializePlist.html#//apple_ref/doc/uid/10000048i-CH7-SW1

You could also use core data, which is what I'm using in my app, or SQLite. But if you are only storing "hundreds" of settings I wouldn't go with either of these options. They're generally only a good choice if the data does not fit in RAM. For data that does fit in memory NSCoding and Plist are significantly faster and easier to work with.

And also read up on "Where You Should Put Your App‘s files": https://developer.apple.com/library/ios/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html

Abhi Beckert
  • 32,787
  • 12
  • 83
  • 110
  • 1
    I agree almost at everything, there is only one thing that I like to point out or have more clues about it: '/Library which is not a safe location' In my opinion this is only valid for the Caches subdirectory that can remove its contents if the system reclaim more disk space. I can't find any hints on documentation that says that NSUserDefault can be deleted, except for removing the app. – Andrea Aug 10 '14 at 10:37
  • 1
    @Andrea in the last link I provided Apple talks about every location in Library where you can store data, and Application Support is the only valid one for this use case. And they classify "support files" as "files your application downloads or generates and can recreate as needed". This means they reserve the right to delete them, if not now then in some future version of the iOS. – Abhi Beckert Aug 10 '14 at 11:03
  • IMO the encryption concern makes no sense, other than that 100%. – zaph Aug 11 '14 at 22:16
  • 2
    @Zaph have you seen how data protection works on iOS? There are some protection modes where data cannot be read while the app is running in the background because the private key for data encryption does not exist and cannot be generated. The public key does exist however, so you can write to the disk but not read from it — and this does appear to be related to background execution of the app. Potentially that could cause `NSUserDefaults` to fall back to deleting it's plist file, restoring to default settings. It's just a theory, but a plausible one I think. – Abhi Beckert Aug 11 '14 at 22:27
  • Actually, it gets stored to Library/Preferences within the application sandbox. – quellish Aug 14 '14 at 23:50
3

I think that using NSUserDefaults to save 100 key/data is probably wrong in in the first place from a development view.
The most right way is save login/sesistive data in the keychain (where you have encryption for free) and the rest in documents dir using a serialized plist. In the NSUserDefaults I would save something like the path of the preferences file or the version number of the preference file.
Your app can be restarted after a crash in background if there is a trigger that realauch it such as CoreLocation or notification.
I really doubt that NSUserDefaults is not available on background (it also thread safe), I assume it would be a problem so big that SO will have plenty of similar questions.
The fact you are receiving a memory warning on the background is probably related to the cause of crashes. What does the crash log say? can you post it?
What's the memory footprint when your app get suspended? Do you have some methods triggered by memory warning or app lifecycle notifications?

Andrea
  • 26,120
  • 10
  • 85
  • 131
0

I think using NSUserDefaults to save 100 key/value is a wrong approach. But if you use NSUserDefaults to save this 100 key/value and you read The defaults in application:didFinishLaunchingWithOptions: Then you just restore your also read it in

- (void)applicationWillEnterForeground:(UIApplication *)application;
KlimczakM
  • 12,576
  • 11
  • 64
  • 83
BHASKAR
  • 1,201
  • 1
  • 9
  • 20