7

I've been getting some odd reports for my app where the application settings stored into the NSUserDefaults is being cleared. The reports have all been on iOS 7s

I know you can manually clear the NSUserDefaults by either uninstalling or making a call to

NSString *appDomain = [[NSBundle mainBundle] bundleIdentifier];

[[NSUserDefaults standardUserDefaults] removePersistentDomainForName:appDomain];

But are there any other known causes for an app to clear its settings ?

Popeye
  • 11,839
  • 9
  • 58
  • 91
loadedion
  • 2,217
  • 19
  • 41
  • 1
    On my jailbroken iPhone many apps loose their NSUserdefaults after a system crash. Did you check if its not from jailbroken iPhones? – Selvin Apr 18 '14 at 16:02
  • Not that I know of, although most of these reports have been through development builds, meaning they were manually installed outside of the App Store. I'll have to check if all of them were. – loadedion Apr 18 '14 at 16:10
  • Are certain they are being written in the first place? – Mike D Apr 18 '14 at 16:12
  • Yes. The first time the app starts, a one-time welcome `ViewController` is shown and a flag is set in the `NSUserDefaults` to prevent it from being displayed again. I've been getting reports that the welcome is seen again (and that the other app settings have been reset) – loadedion Apr 18 '14 at 16:21
  • 1
    Don't forget that in order to have the defaults saved right away you need to call -(void)synchronize. [[NSUserDefaults standardDefaults] synchronize]; – Karim Apr 18 '14 at 16:26
  • 1
    Can I know, apart from this, what other values you are storming in defaults. If the iOS detects a memory pressure, it may clear NSUserDefaults, but not just limited to this. So make sure you are not storing a big chunk if data in defaults. Better use NSDocumentsDirectory for high volume of data. – Balram Tiwari Apr 18 '14 at 16:28
  • In addition to the flag I'm storing booleans (~60) and a string array (variable size). How large is this limit? – loadedion Apr 18 '14 at 16:53
  • 1
    Since iOS 7, the NSUserDefaults are not automatically synchronized when the app goes into background. So, is the real issue simply that you need to call `[[NSUserDefaults standardUserDefaults] synchronize];` when your app goes into background (or at another occasion)? I.e., is the real issue that the settings were not correctly saved in the first place? – DarkDust Apr 18 '14 at 21:44
  • @DarkDust that isn't the case, unfortunately. There is a call to synchronize the NSUserDefaults after every modification. And what I'm ending up with is not just a few erased settings, but all of them. – loadedion Apr 21 '14 at 16:14
  • Sounds like the classical *"not searching where I should"* error kind. Double check your save/read code, `NSUserDefaults` probably not the culprit... – Vincent Guerci Apr 23 '14 at 12:33
  • 1
    Have you been able to fix the issue? I am having the same problem for a while now and can't find the source of the problem. – Daniel Jul 05 '14 at 12:38
  • I'm afraid not, I'm running based off of user reports where it appears the app has cleared all of the data stored in the `NSUserDefaults`. I've been unable to reproduce it, so the question remains unanswered for now. – loadedion Jul 07 '14 at 23:32

1 Answers1

1

If you don't want to get your data deleted, then you should use KeyChain to store values. A good way to get started: Using KeyChain

Below I am providing an example code how to store and get data back from the KeyChain

Importing required frameworks

#import <Security/Security.h>

Storing values to KeyChain

NSString *key = @"Full Name";
NSString *value = @"Steve Jobs";
NSData *valueData = [value dataUsingEncoding:NSUTF8StringEncoding];
NSString *service = [[NSBundle mainBundle] bundleIdentifier];
NSDictionary *secItem = @{
                              (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, (__bridge id)kSecAttrService : service,
                              (__bridge id)kSecAttrAccount : key,
                              (__bridge id)kSecValueData : valueData,
                              };
    CFTypeRef result = NULL;
    OSStatus status = SecItemAdd((__bridge CFDictionaryRef)secItem, &result);
if (status == errSecSuccess)
{
     NSLog(@"Successfully stored the value");
}
else{
     NSLog(@"Failed to store the value with code: %ld", (long)status);
}

Getting values back from KeyChain

NSString *keyToSearchFor = @"Full Name";
NSString *service = [[NSBundle mainBundle] bundleIdentifier];
NSDictionary *query = @{
                        (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, (__bridge id)kSecAttrService : service,(__bridge id)kSecAttrAccount : keyToSearchFor,
                        (__bridge id)kSecReturnAttributes : (__bridge id)kCFBooleanTrue, };
CFDictionaryRef valueAttributes = NULL;
OSStatus results = SecItemCopyMatching((__bridge CFDictionaryRef)query,
                                       (CFTypeRef *)&valueAttributes);
NSDictionary *attributes =
(__bridge_transfer NSDictionary *)valueAttributes;
if (results == errSecSuccess){
    NSString *key, *accessGroup, *creationDate, *modifiedDate, *service;
    key = attributes[(__bridge id)kSecAttrAccount];
    accessGroup = attributes[(__bridge id)kSecAttrAccessGroup]; creationDate = attributes[(__bridge id)kSecAttrCreationDate]; modifiedDate = attributes[(__bridge id)kSecAttrModificationDate]; service = attributes[(__bridge id)kSecAttrService];
    NSLog(@"Key = %@\n \ Access Group = %@\n \
          Creation Date = %@\n \
          Modification Date = %@\n \
          Service = %@", key, accessGroup, creationDate, modifiedDate, service);
}
else
{
    NSLog(@"Error happened with code: %ld", (long)results);
}
E-Riddie
  • 14,660
  • 7
  • 52
  • 74