Файлы исчезают из NSLibraryDirectory
Я храню некоторые файлы в каталоге Library в приложении для iOS, используя следующие методы для его создания. В конце концов, я могу позвонить [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;
}
2 ответа
В коде, который вы разместили, первая проблема здесь:
if (![fileManager fileExistsAtPath:dir isDirectory:&isDir] && isDir)
В точке, где это оценивается, isDir
по умолчанию будет NO, и будет установлен в NO, если файл не существует или не является каталогом. Это предотвратит создание каталога. Удалить && isDir
или изменить на || !isDir
чтобы получить логику, которую вы хотите.
Теперь к вашему первоначальному вопросу:
Является ли это (подкаталог NSLibraryDirectory) безопасным местом для хранения постоянных файлов?
Да. NSLibraryDirectory
резервное копирование по умолчанию. Для соблюдения Руководства по хранению данных iOS приложение не должно хранить данные, созданные пользователем, в этом месте, но это безопасное место для хранения данных приложения. NSApplicationSupportDirectory
каталог, который обычно находится в пределах NSLibraryDirectory
и является предпочтительным местом для хранения данных такого рода. Данные в этом месте будут сохранены и перенесены во время обновления приложений и ОС.
Руководство по хранению данных iOS, Руководство по программированию файловой системы и Руководство по программированию приложений для iOS содержат рекомендации о том, куда помещать файлы и как они будут сохраняться из стандартных расположений файловой системы.
Если эти файлы не NSURLIsExcludedFromBackupKey
/kCFURLIsExcludedFromBackupKey
значение метаданных ресурса изменено. Тогда это становится намного сложнее.
Файлы, исключенные из резервной копии
Как правило, если для резервного копирования файла вне каталога "Документы" система предполагает, что она также может очистить его при нехватке места или других условиях. Вот почему настройка NSURLIsExcludedFromBackupKey
ДА для файла позволяет файлу сохраняться даже в условиях низкого хранения. Если ваше приложение установлено NSURLIsExcludedFromBackupKey
Если ДА за файл, ваше приложение принимает на себя ответственность за срок его службы.
Уловка здесь в том, что процесс резервного копирования и процесс очистки не следуют одной и той же логике. Документация Apple указывает, что в целях управления режимом резервного копирования можно установить NSURLIsExcludedFromBackupKey
в каталоге. Дочерние элементы этого каталога будут эффективно наследовать это значение ресурса (на практике это может быть неточным). Однако процесс очистки, похоже, не имеет такого же поведения. Он может не проверять резервные исключения родительских каталогов и применять его к дочерним элементам, и, как следствие, если файл не имеет NSURLIsExcludedFromBackupKey
явно установить его можно очистить.
Это становится еще сложнее. Если вы должны были прочитать документацию для постоянного NSURLIsExcludedFromBackupKey
вы бы увидели:
Некоторые операции, обычно выполняемые с пользовательскими документами, приводят к тому, что это свойство сбрасывается в false; следовательно, не используйте это свойство в пользовательских документах.
На самом деле это относится гораздо больше, чем к пользовательским документам. Например, если вы должны выполнить атомарную запись в файл, такой как:
[thing writeToURL:URL atomically:YES encoding:NSUTF8StringEncoding error:&error]
Если файл в URL
имел NSURLIsExcludedFromBackupKey
установите значение YES перед записью, теперь будет отображаться значение NO. Подобная атомарная запись сначала создает временный файл, записывает в него и заменяет исходный файл новым. При этом флаги файловых и URL-ресурсов не сохраняются. Оригинальный файл имел NSURLIsExcludedFromBackupKey
Значение ресурса установлено, вновь созданный файл в том же месте теперь нет. Это только один пример; многие API-интерфейсы Foundation выполняют подобные атомарные записи неявно.
Есть сценарии, где это становится еще более сложным. Когда приложение обновляется, оно устанавливается в новое место с новым путем к контейнеру приложения. Данные внутри старого контейнера приложения переносятся. Есть несколько гарантий относительно того, что может или не может быть перенесено как часть процесса обновления. Это может быть все, это могут быть только некоторые вещи. В частности, нет указаний относительно того, как файлы или каталоги, помеченные NSURLIsExcludedFromBackupKey
атрибут ресурса будет обработан. На практике кажется, что это часто наименее вероятные файлы для миграции, и когда они переносятся, NSURLIsExcludedFromBackupKey
атрибут редко сохраняется.
Обновления ОС также являются проблемой. Исторически беспроводные обновления были проблематичными и вызвали NSURLIsExcludedFromBackupKey
атрибут ресурса, который будет эффективно очищен или проигнорирован. "Основное" обновление ОС очистит устройство и восстановит его из резервной копии, что эквивалентно миграции на новое оборудование. Файлы, помеченные NSURLIsExcludedFromBackupKey
атрибут ресурса не будет перенесен, и приложению придется их заново создать.
Сценарии обновления описаны в TechNote 2285: тестирование обновлений приложений iOS
Из-за этого при использовании NSURLIsExcludedFromBackupKey
как правило, лучше установить значение для каждого доступа, и, как всегда, это следует делать через API координации файлов (если только вы не пишете в контейнер общей группы, что является совершенно другим набором проблем). Если NSURLIsExcludedFromBackupKey
Значение атрибута ресурса теряется, файлы могут быть удалены в любое время. В идеале приложение не должно зависеть от NSURLIsExcludedFromBackupKey
или как ОС может (или не может!) справиться с этим, но вместо этого спроектировать так, чтобы данные могли быть воссозданы по требованию. Это не всегда возможно.
Из вашего вопроса и кода, который вы разместили, ясно, что вы в какой-то степени зависите NSURLIsExcludedFromBackupKey
обеспечение того, чтобы ваши файлы имели управляемое приложением время жизни. Как видно из вышесказанного, это не всегда так: существует много, много распространенных сценариев, когда значение этого атрибута ресурса может исчезнуть, а вместе с ним и ваши файлы.
Стоит также отметить, что атрибуты NSFileProtection работают одинаково и могут исчезать в тех же сценариях (и некоторых других).
TL; DR; Что я должен делать?
На основе вашего вопроса, кода и описания поведения, которое вы видите:
Настройка
NSURLIsExcludedFromBackupKey
значение в каталоге, содержащем файлы, которые вы хотите сохранить, может оказаться недостаточным для предотвращения их очистки. Было бы целесообразно установитьNSURLIsExcludedFromBackupKey
при каждом доступе к реальным файлам, а не только к родительскому каталогу. Также попытайтесь убедиться, что это значение ресурса установлено после любой записи в файл, особенно через API высокого уровня, который может выполнять атомарные записи и т. Д.Все NSFileManager и операции чтения / записи файлов должны использовать координацию файлов. Даже в однопоточном приложении будут другие процессы, взаимодействующие с "вашими" файлами. Такие процессы, как демоны, которые запускают резервные копии или удаляют файлы в условиях ограниченного пространства. Между вашими
-fileExistsAtPath:
и-setResourceValue:forKey:error:
другой процесс может изменить, удалить или переместить ваш файл и его атрибуты.-setResourceValue:forKey:error:
на самом деле вернет YES и не будет ошибки во многих случаях, когда он ничего не сделал, например, файл не существует.Файлы и каталоги помечены
NSURLIsExcludedFromBackupKey
ответственность за управление приложением. Приложение все еще должно очищать эти файлы или их содержимое в определенное время или устанавливать ограничения на их рост. Если вы посмотрите на информацию об использовании диска для каждого приложения на устройстве, вы, вероятно, сможете угадать названия некоторых приложений, которые делают это неправильно.Протестируйте сценарии обновления, как описано в TechNote 2285: Тестирование обновлений приложений iOS. довольно часто. В идеале iOS Simulator должен иметь возможность "Симулировать мало дискового пространства", аналогичную симуляции предупреждений в памяти, но в настоящее время это не так.
Если это вообще возможно, измените логику приложения, чтобы воссоздать эти файлы, если они пропали без вести.
В документации, на которую вы ссылаетесь, указано, что
Критические данные должны храниться в каталоге /Documents. Критические данные - это любые данные, которые не могут быть воссозданы вашим приложением, такие как пользовательские документы и другой пользовательский контент.
Также упоминается, что
Кэшированные данные должны храниться в каталоге /Library/Caches. Примеры файлов, которые вы должны поместить в каталог Caches, включают (но не ограничиваются ими) файлы кэша базы данных и загружаемый контент, например, используемый в журналах, газетах и приложениях для карт. Ваше приложение должно иметь возможность корректно обрабатывать ситуации, когда кэшированные данные удаляются системой, чтобы освободить дисковое пространство.
Используемый вами каталог явно не упоминается для хранения пользовательских данных, он используется системой и не сохраняет ваши данные. Оно гарантированно не будет затронуто обновлением вашего приложения, но это все
Чтобы найти папку с документами, вы можете сделать что-то вроде
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsFolderPath = [paths firstObject];