文件从NSLibraryDirectory中消失

时间:2014-11-30 17:42:38

标签: ios objective-c nsdocumentdirectory nslibrarydirectory

我将一些文件存储在iOS应用程序的Library目录中,使用以下方法构建它。最后,我可以调用[MyClass dataDirectory]进行文件处理,一切顺利。然而,我最近发现,一些文件似乎神秘地消失在这个目录之外。根据{{​​3}},情况并非如此。这是存储持久文件的安全位置吗?

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

2 个答案:

答案 0 :(得分:23)

在您发布的代码中,第一个问题出在此处:

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属性很少被保留。

操作系统更新也是一个问题。历史上,Over-the-Air更新存在问题,导致NSURLIsExcludedFromBackupKey资源属性被有效清除或忽略。 A"主要"操作系统更新将清除设备并从备份恢复 - 这相当于迁移到新硬件。标记有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模拟器会有一个模拟低磁盘空间"功能类似于模拟内存警告,但此时却没有。

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

答案 1 :(得分:2)

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

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

您没有明确提到您正在使用的目录用于存储用户数据,系统使用该目录而不保存您的数据。保证您的应用更新不受影响,但就是这样

要查找文档文件夹,您可以执行类似

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