偶尔出现的NSUserDefaults问题

3
我使用NSUserDefaults在用户的iOS设备上持久化大约100个键/值对。每个键/值对都只是一个字符串键和布尔值。这几乎一直很好用。最近,有些用户提到他们的应用程序被“重置”。具体来说,他们的应用程序未能正确读取NSUserDefaults中的数据。我试图理解这是如何发生的。
需要注意的几件事情:
  • 每次更新用户默认设置后,我都会调用synchronize
  • 我没有清除单个条目或整个默认设置的任何代码
  • 默认设置在application:didFinishLaunchingWithOptions:中读取
  • 当应用程序从后台切换到前台时,不会读取默认设置
我在这篇Loom.com博客文章中找到了一些有趣的评论。听起来,当应用程序在后台重新启动时,NSUserDefaults后备plist可能无法访问。我不确定如果在后台崩溃时,后台应用程序是否会重新启动。然而,我很好奇,因为我的应用程序确实在后台崩溃,根据我的崩溃报告服务。此外,此崩溃在收到内存警告后立即发生。 应用程序在后台崩溃后重新启动时(在后台),是否可能无法读取用户默认设置? 非常感谢您对如何诊断此问题的任何建议! 编辑-更多信息:听起来CoreLocation框架可能会导致应用程序在后台崩溃后重新启动。我的应用程序包括一些第三方广告和分析SDK。实际上,这个问题在添加一个特定的SDK后开始出现,该SDK可以使用CoreLocation。

1
键/值对永远消失了吗? - KudoCC
1
这些被重置的键是否有通用名称?可能是第三方覆盖了这些键吗? - Tomer Even
1
似乎很多人都有同样的问题。请查看此链接,希望能够帮到您。 - KudoCC
此外,这个崩溃发生在收到内存警告后立即发生。这意味着您的应用程序没有正确响应内存警告。 - quellish
你的应用程序是否曾因位置变化(或推送通知)而在后台启动?如果启用了“加密休息”功能,则可能会在后台启动应用程序(假设手机已锁定),并且NSUserDefaults是无法访问的。您可以通过[[UIApplication sharedApplication] isProtectedDataAvailable]查询此信息。如果返回NO,则无法访问受保护的数据(或keychain)。 - drunknbass
3个回答

5
iOS会对写入磁盘的数据进行复杂处理,以实现(几乎)无缝加密,因此这种错误是有可能出现的。也许由于某些原因文件无法解密,所以被删除了,导致NSUserDefaults恢复到之前的状态。我不确定这是否是原因,但我认为这很有可能。
另外,请注意NSUserDefaults将数据保存在<Application_Home>/Library中,这不是一个安全的位置。它仅适用于“应用程序下载或生成并可以根据需要重新创建的文件”。
也许更好的存储位置是<Application_Home>/Documents,它适用于“无法通过您的应用程序重新创建的数据”。如果您的用户默认设置足够重要,以至于这成为一个问题,那么它就属于“用户生成内容”,因此应该存储在Documents文件夹中。
因此,我建议放弃使用NSUserDefaults,因为它不能满足您的需求,并通过将NSDictionary写入Documents文件夹来保存数据,使用NSCoding或Binary Plist(确保将其设置为NSPropertyListBinaryFormat_v1_0,因为它不是默认设置,应该在像iOS设备这样的慢闪存储器上使用)。

苹果公司为 NSCoding 和 Plist 序列化提供了良好的文档和示例代码:

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

您也可以使用核心数据(Core Data),这是我在我的应用程序中使用的,或者使用SQLite。但如果您只存储“数百”个设置,则不建议选择这两个选项。通常情况下,只有当数据无法放入内存时才是一个好的选择。对于适合内存的数据,NSCoding和Plist速度更快,易于处理。

此外,请阅读“应将应用程序文件放置在何处”:https://developer.apple.com/library/ios/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html


1
我基本上同意所有的内容,只有一件事情需要指出或者更多的线索:'<Application_Home>/Library'不是一个安全的位置。在我看来,这仅适用于可以在系统回收更多磁盘空间时清除其内容的缓存子目录。我找不到任何提示文档说NSUserDefault可以被删除,除非卸载应用程序。 - Andrea
1
@Andrea,在我提供的最后一个链接中,苹果讨论了可以存储数据的Library中的每个位置,而Application Support是此用例的唯一有效位置。他们将“支持文件”分类为“应用程序下载或生成并可以根据需要重新创建的文件”。这意味着他们保留删除它们的权利,如果不是现在,那么可能会在iOS的某个未来版本中删除。 - Abhi Beckert
在我看来,除了加密问题以外,其他方面都是百分之百的。 - zaph
2
@Zaph,你看过iOS上的数据保护是如何工作的吗?有一些保护模式,在应用程序在后台运行时,数据无法被读取,因为数据加密的私钥不存在且无法生成。公钥确实存在,所以你可以写入磁盘,但不能从中读取——这似乎与应用程序的后台执行有关。潜在地,这可能会导致NSUserDefaults回退到删除其plist文件,恢复默认设置。这只是一个理论,但我认为是一个合理的理论。 - Abhi Beckert
实际上,它被存储在应用程序沙盒中的Library/Preferences目录下。 - quellish

3
我认为从开发的角度来看,使用NSUserDefaults来保存100个键值数据可能是错误的。最正确的方法是将登录/敏感数据保存在钥匙串中(可以免费加密),而将其余数据序列化到文档目录中的plist文件中。在NSUserDefaults中,我会保存诸如首选项文件路径或版本号之类的内容。如果有触发器(如CoreLocation或通知),则您的应用程序可以在崩溃后在后台重新启动。
我非常怀疑NSUserDefaults在后台不可用(它也是线程安全的)。如果真的存在这样的问题,那么Stack Overflow上一定会有很多类似的问题。
背景模式下收到内存警告可能与崩溃的原因有关。崩溃日志上说了什么?你能发一下吗?当您的应用程序被挂起时,内存占用情况如何?您是否有通过内存警告或应用程序生命周期通知触发的某些方法?

0

我认为使用NSUserDefaults来保存100个键值对是错误的方法。 但如果您在application:didFinishLaunchingWithOptions:中读取默认值以保存这100个键值对, 然后您可以恢复它们并在其他地方也能够读取。

- (void)applicationWillEnterForeground:(UIApplication *)application;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接