Файлы исчезают из 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];
Другие вопросы по тегам