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应用程序。 决不。