【问题标题】:Files disappearing from NSLibraryDirectory文件从 NSLibraryDirectory 中消失
【发布时间】:2015-01-28 17:42:52
【问题描述】:

我将一些文件存储在 iOS 应用程序的 Library 目录中,使用以下方法构建它。最后,我可以打电话给[MyClass dataDirectory] 来处理我的文件,一切都很好。然而,我最近发现一些文件似乎神秘地从这个目录中消失了。根据the documentation,情况不应该如此。这是存储持久文件的安全地方吗?

该目录的控制台输出为:~/var/mobile/Containers/Data/Application/{id}/Library/Data

+ (NSString*)libraryDirectory
{
    return [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];
}

+ (NSString*)dataDirectory
{
    NSString* dir = [[self libraryDirectory] stringByAppendingPathComponent:@"Data"];
    BOOL isDir=NO;
    NSError * error = nil;
    NSFileManager *fileManager = [NSFileManager new];

    if (![fileManager fileExistsAtPath:dir isDirectory:&isDir] && isDir)
    {

        [[NSFileManager defaultManager] createDirectoryAtPath:dir
                                  withIntermediateDirectories:YES
                                                   attributes:nil
                                                        error:&error];
    }

    [self addSkipBackupAttributeToItemAtURL:[NSURL fileURLWithPath:dir isDirectory:YES]];

    if (error != nil) {
        DDLogError(@"Fatal error creating ~/Library/Data directory: %@", error);
    }
    return dir;
}

还有跳过方法:

+ (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
    if ([[NSFileManager defaultManager] fileExistsAtPath:[URL path]])
    {
        assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);

        NSError *error = nil;
        BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
                                      forKey: NSURLIsExcludedFromBackupKey error: &error];
        if(!success){
            DDLogError(@"Error excluding %@ from backup %@", [URL lastPathComponent], error);
        }
        return success;
    }
    return YES;
}

【问题讨论】:

  • 你在 iOS 8 remus 上看到了吗?
  • 您的应用程序是否有可能尝试保留从 +libraryDirectory 返回的值?在 iOS 8 中,该目录的完整路径可以在应用程序启动之间更改。
  • 每次我引用文件时,我都会调用+libraryDirectory,所以只要下次应用启动时文件在那个位置,就不会有问题。
  • 是的,这就是你应该做的,虽然不是每个人都这样做(咳咳,比如...*咳嗽文件协调咳嗽*)。我会尽快发布您问题的完整答案。
  • 很确定我知道你的问题的根源是什么,尽管它仍然很难复制或预测它何时会发生。我会尽快发布一个应该有帮助的答案。

标签: ios objective-c nsdocumentdirectory nslibrarydirectory


【解决方案1】:

在您发布的代码中,第一个问题在这里:

if (![fileManager fileExistsAtPath:dir isDirectory:&isDir] && isDir)

在评估时,isDir 将默认为 NO,如果文件不存在或不是目录,则将设置为 NO。这将阻止创建目录。删除&& isDir 或更改为|| !isDir 以获得您想要的逻辑。

现在回到你原来的问题:

这是(NSLibraryDirectory 的子目录)存储持久文件的安全地方吗?

是的。 NSLibraryDirectory 默认备份。为了遵守iOS Data Storage Guidelines,应用程序不应在该位置存储用户创建的数据,但它是存储应用程序数据的安全位置。 NSApplicationSupportDirectory 是一个通常在NSLibraryDirectory 内的目录,是存储此类数据的首选位置。将备份该位置内的数据,并在应用程序和操作系统更新期间进行迁移。

iOS Data Storage GuidelinesFile System Programming GuideApp Programming Guide for iOS 都提供有关将文件放置在何处以及如何从标准文件系统位置备份它们的指导。

除非这些文件的 NSURLIsExcludedFromBackupKey/kCFURLIsExcludedFromBackupKey 资源元数据值已更改。然后它变得更加复杂。

“从备份中排除”的文件

通常,如果可以备份 Documents 目录之外的文件,则系统假定它也可以在空间不足或其他情况下清除它。这就是为什么在文件上将 NSURLIsExcludedFromBackupKey 设置为 YES 可以使文件即使在低存储条件下也能持久存在。如果您的应用程序将某个文件的 NSURLIsExcludedFromBackupKey 设置为 YES,则您的应用程序将对该文件的生命周期负责。

这里的问题是备份过程和清除过程不遵循相同的逻辑。 Apple 的文档表明,为了控制备份行为,可以在目录上设置NSURLIsExcludedFromBackupKey。该目录的子目录将有效地继承该资源值(实际上,这可能不准确)。但是,清除过程似乎没有相同的行为。它可能不会检查父目录的备份排除并将其应用于子目录,因此如果文件没有明确设置NSURLIsExcludedFromBackupKey,则可能会被清除。

这变得更加复杂。如果你阅读documentation for the constant NSURLIsExcludedFromBackupKey 你会看到:

通常对用户文档进行的一些操作会导致此属性重置为 false;因此,请勿在用户文档上使用此属性。

这实际上不仅仅适用于用户文档。例如,如果您要对文件执行原子写入,例如:

[thing writeToURL:URL atomically:YES encoding:NSUTF8StringEncoding error:&error]

如果URL 的文件在写入之前将NSURLIsExcludedFromBackupKey 设置为YES,那么现在它似乎设置为NO。像这样的原子写入将首先创建一个临时文件,写入该文件,然后用新文件替换原始文件。这样做时,不会保留文件和 URL 资源标志。原始文件设置了 NSURLIsExcludedFromBackupKey 资源值,而在同一位置新创建的文件现在没有。这只是一个例子;许多 Foundation API 都隐含地执行这样的原子写入。

在某些情况下,情况会变得更加复杂。当应用程序更新时,它会安装到具有新应用程序容器路径的新位置。旧应用程序容器内的数据被迁移。对于作为更新过程的一部分可能迁移或不迁移的内容,几乎没有保证。它可能是一切,也可能只是一些东西。特别是没有关于如何处理标记有NSURLIsExcludedFromBackupKey 资源属性的文件或目录的指导。在实践中,这些似乎通常是最不可能被迁移的文件,并且在迁移它们时,NSURLIsExcludedFromBackupKey 属性很少被保留。

操作系统更新也是一个问题。过去,无线更新一直存在问题,并导致NSURLIsExcludedFromBackupKey 资源属性被有效清除或忽略。 “重大”操作系统更新将清除设备并从备份中恢复——这相当于迁移到新硬件。标有NSURLIsExcludedFromBackupKey 资源属性的文件将不会被迁移,应用程序必须重新创建它们。

更新场景在TechNote 2285: Testing iOS App Updates中描述

因此,当使用NSURLIsExcludedFromBackupKey 时,通常最好在每次访问时设置值,并且一如既往地应该通过File Coordination APIs 来完成(除非您正在写入共享组容器,这完全是不同的问题集)。如果NSURLIsExcludedFromBackupKey 资源属性值丢失,可以随时清除文件。理想情况下,应用程序不应依赖于NSURLIsExcludedFromBackupKey 或操作系统可能(或可能不!)处理它的方式,而是设计成可以按需重新创建数据。这可能并不总是可能的。

从您的问题和您发布的代码中可以清楚地看出,您在某种程度上依赖于NSURLIsExcludedFromBackupKey,以确保您的文件具有应用程序控制的生命周期。正如您从上面看到的那样,情况可能并非总是如此:有很多很多常见的场景,资源属性值可能会消失,您的文件也会随之消失。

同样值得注意的是,NSFileProtection 属性的工作方式相同,并且可以在相同的场景中消失(以及更多场景)。

TL;DR;我该怎么办?

根据您的问题、代码和您看到的行为描述:

  • 在包含您有兴趣保留的文件的目录上设置NSURLIsExcludedFromBackupKey 值可能不足以防止它们被清除。明智的做法是在每次访问实际文件时设置NSURLIsExcludedFromBackupKey,而不仅仅是父目录。还要尝试确保在对文件进行任何写入后设置此资源值,尤其是通过可能进行原子写入的高级 API 等。

  • 所有 NSFileManager 和文件读/写操作都应该使用文件协调。即使在单线程应用程序中,也会有其他进程与“您的”文件交互。在空间不足的情况下运行备份或清除文件的守护进程等进程。在您的-fileExistsAtPath:-setResourceValue:forKey:error: 之间,另一个进程可能会更改、删除或移动您的文件及其属性。 -setResourceValue:forKey:error: 实际上会返回 YES 并且在很多情况下它什么都不做,比如文件不存在,不会出错。

  • 标有NSURLIsExcludedFromBackupKey 的文件和目录由应用程序负责管理。应用程序仍应在某个适当的时间清除这些文件或其内容,或对其增长设置限制。如果您查看设备上每个应用程序的磁盘使用信息,您可能会猜到一些不正确执行此操作的应用程序的名称。

  • TechNote 2285: Testing iOS App Updates 中所述测试更新方案。经常。理想情况下,iOS 模拟器将具有类似于模拟内存警告的“模拟低磁盘空间”功能,但目前没有。

  • 如果可能,更改应用程序逻辑以在这些文件丢失时重新创建这些文件。

【讨论】:

  • @matt 现在 是一个答案
  • 超级答案@quellish,非常感谢!今晚要努力解决这个问题。 !isDir 也不错。
  • 好答案,我在缓存目录中的 sqlite 文件上设置此属性,但它仍在被清除。根据此信息将数据写入 sqlite 文件时可能会重置。
  • 一位 Apple 工程师刚刚对我说这个答案是正确的。在磁盘空间不足的情况下文件是否被删除与 NSURLIsExcludedFromBackupKey 设置无关。 只有 Library/Caches 和 Tmp 目录中的文件会在磁盘空间不足时被删除。 与备份完全分开,有一种机制可以删除明确存储在 iCloud 中的文件。但这是普通 iCloud API 的一部分,应用程序需要通过 API 明确授予权限。
  • 这是一个令人难以置信的答案。谢谢;文档不太清楚标志可能如何变化,这很有帮助!
【解决方案2】:

在您链接的文档中,
关键数据应存储在 /Documents 目录中。关键数据是您的应用无法重新创建的任何数据,例如用户文档和其他用户生成的内容。

还提到
缓存数据应该存储在 /Library/Caches 目录中。您应该放在 Caches 目录中的文件示例包括(但不限于)数据库缓存文件和可下载内容,例如杂志、报纸和地图应用程序使用的内容。您的应用应该能够优雅地处理缓存数据被系统删除以释放磁盘空间的情况。

您使用的目录未明确提及用于存储用户数据,它由系统使用并且不保存您的数据。保证不会因您的应用更新而受到影响,但仅此而已

要查找文档文件夹,您可以执行以下操作

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 
NSString *documentsFolderPath = [paths firstObject];

【讨论】:

  • 我希望规范确认我使用的位置合适,因为我没有使用/Library/Caches。文档确实声明(在应用更新期间保存的文件部分)/Library 在(至少)应用更新中持续存在。
  • 我认为你有它倒退,@remus。您有一个可以安全存储数据的位置列表,而将其存储在任何其他任何安全期望的地方都是愚蠢的。将您的目录放在更好的位置,例如应用程序支持或文档。然后,如果您的数据开始消失,我们将有话要说!
  • @matt 好吧,重新定位它需要做很多工作;我需要确保自己做出了正确的决定,然后才开始行动。另外,我有点希望将 Documents 与非文档分开。
  • 看,@remus - 你知道你现在遇到了麻烦,很明显有问题。而“有很多工作”的说法只不过是一种难闻的气味。如果这很难改变,那么 that 就是一个错误。最后,我没有说 Documents 或什么都没有;应用程序支持非常合适,是我通常使用(和推荐)的。
  • @matt “支持文件包括您的应用程序下载或生成的文件,您的应用程序可以根据需要重新创建这些文件。”。我可以重新创建它,但希望它能够保证是持久的。它是文档,但我仍然想要一个规范的响应来确认我存储它的位置是不合适的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-25
  • 2019-02-27
  • 2015-10-10
  • 2011-12-31
  • 2016-08-03
相关资源
最近更新 更多