NSUserDefaults refuse to save

2020-07-27 05:02发布

问题:

Start a new single view project and update the main ViewController's viewDidLoad. The intention is to retrieve and increment a value stored in NSUserDefaults and save it.

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSString *key = @"kTheKey";

    NSNumber *number = [[NSUserDefaults standardUserDefaults] objectForKey:key];
    NSLog(@"current value is %@", number);

    NSNumber *incremented = @(number.integerValue + 1);
    NSLog(@"new value will be %@", incremented);

    [[NSUserDefaults standardUserDefaults] setObject:incremented forKey:key];
    [[NSUserDefaults standardUserDefaults] synchronize];

    NSLog(@"reboot");
}

If I force quit the app from Xcode (or in practical use, reboot the device), the defaults are frequently not saved. Here is some sample output:

current value is (null)
new value will be 1
reboot
current value is 1
new value will be 2
reboot
current value is 1
new value will be 2
reboot

There appears to be some time component - if I wait 3+ seconds before rebooting, it is more likely the defaults will save. Note that the first execution was 'allowed' to save by waiting for a few seconds before stopping execution. The second execution was stopped in the first second or two, leading to the unchanged values logged in the the third run. This is re-produceable on my iPad Air 2 running iOS 8.1.

What could account for this?

回答1:

This is normal behavior.

User defaults get queued to save. When you "force quit" the app you're not giving it time to do that.

I assume that on Xcode you mean hitting stop. And you mention exit(0);. Neither of things are "normal" to an iOS app. This type of force quitting should not be done in an iOS app.

When the user quits an app the normal way (multi-task view and sliding the app up) it doesn't actually quit right then. It removes from view as if it does. But the user defaults will then get written out after that. Up to several seconds after. Same when they hit the home button.

The documentation completely explains the app life cycle. Use the messages. And you should force flush out the defaults when these messages are received. Put in your initialization or ViewDidLoad like this:

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(movingToBackground:) name:UIApplicationWillResignActiveNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(movingToForeground:) name:UIApplicationDidBecomeActiveNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateDefaults:) name:UIApplicationWillTerminateNotification object:nil];

Then create the methods movingToBackground, movingToForeground, and updateDefaults like this:

-(void) updateDefaults: (NSNotification *) notification {
     [[NSUserDefaults standardUserDefaults] synchronize];
}