5

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

该目录的控制台输出为:~/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;
}
4

2 回答 2

25

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

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

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

现在回到你原来的问题:

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

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

iOS 数据存储指南文件系统编程指南iOS 应用程序编程指南都提供了有关将文件放置在何处以及如何从标准文件系统位置备份它们的指南。

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

文件“从备份中排除”

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

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

这变得更加复杂。如果您要阅读该常量的文档, NSURLIsExcludedFromBackupKey您会看到:

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

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

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

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

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

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

技术说明 2285中描述了更新方案:测试 iOS 应用程序更新

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

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

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

TL;博士; 我应该怎么办?

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

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

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

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

  • 技术说明 2285:测试 iOS 应用程序更新中所述测试更新方案。经常。理想情况下,iOS 模拟器将具有类似于模拟内存警告的“模拟低磁盘空间”功能,但目前还没有。

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

于 2014-12-05T05:32:06.780 回答
2

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

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

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

要找到文档文件夹,您可以执行类似的操作

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 
NSString *documentsFolderPath = [paths firstObject];
于 2014-12-02T21:02:38.767 回答