文件从NSLibraryDirectory中消失

我正在iOS应用程序的库目录中存储一些文件,使用以下方法来构build它。 最后,我可以调用[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; } 

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

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

在评估过程中, isDir将默认为NO,如果该文件不存在或不是目录,则将设置为NO。 这将阻止目录被创build。 删除&& isDir或更改为|| !isDir || !isDir来获得你想要的逻辑。

现在到你原来的问题:

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

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

适用于iOS的iOS数据存储指南 , 文件系统编程指南和应用程序编程指南都提供了关于在何处放置文件以及如何从标准文件系统位置进行备份的指导。

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

“从备份中排除的文件”

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

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

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

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

这实际上比用户文档更适用。 例如,如果您要在文件上执行primefaces写入,例如:

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

如果URL的文件在写入之前将NSURLIsExcludedFromBackupKey设置为YES,则现在将显示为NO。 像这样的primefaces写入将首先创build一个临时文件,写入到该文件,并用新文件replace原始文件。 这样做,文件和URL资源标志不会被保留。 原始文件设置了NSURLIsExcludedFromBackupKey资源值,现在在同一位置新创build的文件没有。 这只是一个例子。 许多基础API执行像这样暗含的primefaces写入。

有些情况下,这变得更加复杂。 当应用程序更新时,它将被安装到具有新的应用程序容器path的新位置。 旧应用程序容器中的数据被迁移。 作为更新过程的一部分,可以或不可以迁移的内容几乎没有保证。 这可能是一切,也可能只是一些事情。 尤其是没有关于如何处理标记NSURLIsExcludedFromBackupKey资源属性的文件或目录的指导。 实际上,这些文件通常是最不可能被迁移的文件,迁移时很less保留NSURLIsExcludedFromBackupKey属性。

操作系统更新也是一个问题。 从历史上看,无线更新存在问题,导致NSURLIsExcludedFromBackupKey资源属性被有效清除或忽略。 “主要”操作系统更新将清除设备并从备份恢复 – 相当于迁移到新硬件。 标有NSURLIsExcludedFromBackupKey资源属性的文件将不会被迁移,应用程序将不得不重新创build它们。

技术说明2285:testingiOS应用程序更新中介绍了更新scheme

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

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

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

TL; DR; 我该怎么办?

根据您的问题,代码以及您所看到的行为描述:

  • 在包含您感兴趣的文件的目录中设置NSURLIsExcludedFromBackupKey值可能不足以防止它们被清除。 设置NSURLIsExcludedFromBackupKey对每个对实际文件的访问是明智的,而不仅仅是一个父目录。 在任何对文件的写入之后,尤其是通过可能正在进行primefaces写入的高级API,也要确保设置此资源值。

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

  • 标有NSURLIsExcludedFromBackupKey文件和目录是应用程序pipe理的责任。 应用程序仍然应该在适当的时候清除这些文件或其内容,或者限制其增长。 如果您查看设备上的每个应用程序磁盘使用情况信息,则可能会猜到某些不正确执行此操作的应用程序的名称。

  • testing更新scheme,如TechNote 2285:testingiOS应用程序更新中所述 。 经常。 理想情况下,iOS模拟器将具有类似于模拟内存警告的“模拟低磁盘空间”能力,但是目前情况并非如此。

  • 如果可能的话,改变应用程序逻辑来重build这些文件,如果它们丢失的话。

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

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

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

要find文件夹,你可以做类似的事情

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