NSUserDefaults在iOS 8中不可靠

我有一个应用程序使用[NSUserDefaults standardUserDefaults]来存储会话信息。 通常,这些信息在应用程序启动时进行检查,并在应用程序退出时更新。 我发现它似乎在iOS 8中不可靠的工作。

我目前正在iPad 2上进行testing,但如果需要的话,我可以在其他设备上进行testing。

有些时候,在退出之前写入的数据不会在应用程序启动时持续存在。 同样,在退出之前移除的键在启动后有时似乎存在。

我已经写了下面的例子,试图说明这个问题:

- (void)viewDidLoad { [super viewDidLoad]; NSData *_dataArchive = [[NSUserDefaults standardUserDefaults] objectForKey:@"Session"]; NSLog(@"Value at launch - %@", _dataArchive); NSString *testString = @"TESTSTRING"; [[NSUserDefaults standardUserDefaults] setObject:testString forKey:@"Session"]; [[NSUserDefaults standardUserDefaults] synchronize]; _dataArchive = [[NSUserDefaults standardUserDefaults] objectForKey:@"Session"]; NSLog(@"Value after adding data - %@", _dataArchive); [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"Session"]; [[NSUserDefaults standardUserDefaults] synchronize]; _dataArchive = [[NSUserDefaults standardUserDefaults] objectForKey:@"Session"]; NSLog(@"Value before exit - %@", _dataArchive); exit(0); } 

运行上面的代码,我(通常)得到下面的输出(这是我所期望的):

 Value at launch - (null) Value after adding data - TESTSTRING Value after deleting data - (null) 

如果我然后注释掉删除密钥的行:

 //[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"Session"]; //[[NSUserDefaults standardUserDefaults] synchronize]; 

运行应用程序三次,我期望看到:

 Value at launch - (null) Value after adding data - TESTSTRING Value after deleting data - TESTSTRING Value at launch - TESTSTRING Value after adding data - TESTSTRING Value before exit - TESTSTRING Value at launch - TESTSTRING Value after adding data - TESTSTRING Value before exit - TESTSTRING 

但是我实际看到的输出是:

 Value at launch - (null) Value after adding data - TESTSTRING Value after deleting data - TESTSTRING Value at launch - (null) Value after adding data - TESTSTRING Value after deleting data - TESTSTRING Value at launch - (null) Value after adding data - TESTSTRING Value after deleting data - TESTSTRING 

例如它似乎不会更新退出应用程序的值。

编辑 :我已经在运行iOS 7.1.2的iPad 2上testing了相同的代码; 它似乎每次都能正常工作。

TLDR – 在iOS 8中,[NSUserDefaults standardUserDefaults]工作不可靠吗? 如果有的话,是否有解决方法?

iOS 8引入了许多对NSUserDefaults的行为更改。 尽pipeNSUserDefaults API变化不大,但行为已经以与您的应用程序相关的方式发生了变化。 例如,使用-synchronize不鼓励(总是)。 对Foundation和CoreFoundation其他部分的添加更改(如文件协调)和与共享容器相关的更改可能会影响您的应用程序以及您对NSUserDefaults的使用。

写入NSUserDefaults特别是因为这个而改变了。 写入需要更长的时间,并且可能有其他进程竞争访问应用程序的用户默认存储。 如果在应用程序退出时试图写入NSUserDefaults ,则在某些情况下提交写入之前,可能会终止应用程序。 在你的例子中强制终止使用exit(0)很可能会激发这种行为。 通常,退出应用程序时,系统可以执行清理并等待未完成的文件操作完成 – 当使用exit()或debugging程序终止应用程序时,可能不会发生。

一般来说NSUserDefaults在iOS 8上正确使用时是可靠的。

这些更改在OS X 10.10的Foundation发行说明中进行了描述(目前,iOS 8没有单独的Foundation发行说明)。

它看起来像iOS 8不喜欢在NSUserDefaults中设置string。 尝试在保存之前将string编码到NSData中。

保存时:

 [[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:testString] forKey:@"Session"]; 

阅读时:

 NSData *_data = [[NSUserDefaults standardUserDefaults] objectForKey:@"Session"]; NSString *_dataArchive = [NSKeyedUnarchiver unarchiveObjectWithData:_data]; 

希望这可以帮助。

正如gnasher729所说,不要叫exit()。 iOS8中的NSUserDefaults 可能存在问题,但调用exit()将无法正常工作。

你应该看到David Smith对NSUserDefaults的评论( https://gist.github.com/anonymous/8950927 ):

终止一个应用程序exception(内存压力杀死,崩溃,在Xcode停止)就像git reset – hard HEAD,离开

我发现NSUserDefaults在iOS 8.4上使用套件名称来创build实例而不是依赖于standardUserDefaults时performance良好。

NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"MySuiteName"];

我有与iOS 8相同的问题,唯一的解决办法是延迟执行退出()函数一段时间(例如:0.1秒)使用:

 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC / 10), dispatch_get_main_queue(), ^{ exit(0); }); 

或创build一个方法,然后使用performSelector调用它:withObject:afterDelay:

 - (void)exitApp { exit(0); } [self performSelector:@selector(exitApp) withObject:nil afterDelay:0.1]; 

由于这是一个企业应用程序,而不是一个App Store应用程序,您可以尝试:

 @interface UIApplication() -(void) _terminateWithStatus:(int)status; @end 

然后打电话给:

 [UIApplication.sharedApplication _terminateWithStatus:0]; 

它使用的是未公开的API,因此可能无法在之前或未来版本的iOS中使用。

这是在模拟器中的bug。这个bug也存在于设备上的iOS8 beta4之前。但是在设备上这个bug已经解决了,但是它现在存在于模拟器上。他们也改变了模拟器的目录结构。如果你重置模拟器,在iOS8设备上也可以正常工作。

我发现它在基础框架参考,认为这将是有益的:

NSUserDefaults类提供了访问常见types(如浮点数,双精度,整数,布尔值和URL)的便捷方法。 一个默认对象必须是一个属性列表,也就是一个实例(或集合的实例组合):NSData,NSString,NSNumber,NSDate,NSArray或NSDictionary。 如果要存储任何其他types的对象,则通常应将其存档以创build一个NSData实例。 有关更多详情,请参阅首选项和设置编程指南。

正如其他人指出,使用exit()和一般退出你的应用程序是在iOS的真正坏主意。

但是我可能知道你有什么要处理的。 我们也开发了一个企业应用程序,尽pipe我们试图说服客户在iOS中违反了所有规则和最佳实践,但他们坚持要我们closures应用程序。

我们使用这段代码而不是exit():

 UIApplication *app = [UIApplication sharedApplication]; [app performSelector:@selector(suspend)]; 

顾名思义,它只是暂停应用程序,就好像用户按下home键一样。 因此,您的保存方法可能能够正确完成。

我还没有为你的特定情况testing过这个解决scheme,而且我不确定暂停对你来说是否足够,但是对于我们来说它已经工作了。

我已经通过仅在主线程中更改NSUserDefaults来解决类似的问题。

我面临同样的问题。 我打电话来解决它

[[NSUserDefaults standardUserDefaults] synchronize];

打电话之前

[[NSUserDefaults standardUserDefaults] stringForKey:@"my_key"]

事实certificate,不仅在设置之后而且在获取之前,还必须调用synchronize

在iOS应用程序中调用exit()是一种犯罪行为,正如您注意到的那样,它会受到惩罚。 你永远不会自己放弃iOS应用程序。 决不。