Получил отличный ответ от одного из разработчиков Apple. Будет переносить мою существующую модель в Core Data в течение следующих нескольких недель. (StackOverflow перепутан с некоторыми списками / форматированием, но по большей части он все еще очень удобочитаем.)
Я начну свой ответ со слов о журналировании HFS Plus. С момента появления журналирования в Mac OS X 10.2.x гарантия правильности файловой системы Mac OS X заключалась в том, что - независимо от паники ядра, сбоев питания и т. Д. - операции с файловой системой приведут к одному из двух результатов:
o либо операция будет откатываться журналом, в этом случае будет так, как если бы операция завершилась успешно
o или операция будет отменена, и в этом случае будет так, как будто операция никогда не выполнялась
Эта гарантия имеет два критических ограничения:
o Применяется к отдельным операциям файловой системы (создает, удаляет, перемещает и т. Д.), А не к группам операций.
o Применяется только к логической структуре файловой системы, но не к данным в файлах.
Короче говоря, цель журнала - предотвратить общее повреждение файловой системы, а не повреждение конкретного файла.
Имея это в виду, давайте посмотрим на поведение - [NSData writeToFile: options: error:]. Его поведение может быть очень сложным, но в типичном случае довольно простым. Один из способов исследовать это - написать некоторый код и посмотреть на поведение его файловой системы. Например, вот несколько тестовых кодов:
- (IBAction)testAction:(id)sender
{
BOOL success;
NSData * d;
struct stat sb;
d = [@"Hello Cruel World!" dataUsingEncoding:NSUTF8StringEncoding];
assert(d != nil);
(void) stat("/foo", &sb);
success = [d writeToFile:@"/tmp/WriteTest.txt" options:NSDataWritingAtomic error:NULL];
(void) stat("/foo", &sb);
assert(success);
}
Два вызова являются просто маркерами; они позволяют легко увидеть, какие операции файловой системы генерируются с помощью -writeToFile: options: error :.
Вы можете посмотреть на поведение файловой системы, используя:
$ sudo fs_usage -f filesys -w WriteTest
где «WriteTest» - это название моей тестовой программы.
Вот выдержка из полученного вывода fs_usage:
14: 33: 10,317 stat [2] / foo
14: 33: 10.317 lstat64 private / tmp / WriteTest.txt
14: 33: 10.317 открытый F = 5 (RWC__E) private / tmp / .dat2f56.000
14: 33: 10,317 записать F = 5 B = 0x12
14: 33: 10.317 fsync F = 5
14: 33: 10.317 закрыть F = 5
14: 33: 10.318 переименовать личное / tmp / .dat2f56.000
14: 33: 10.318 chmod private / tmp / WriteTest.txt
14: 33: 10,318 stat [2] / foo
Вы можете ясно видеть вызовы "stat", которые окружают -writeToFile: options: error: call, означая, что весь материал между этими вызовами генерируется -writeToFile: options: error:.
Что это делает? Ну, на самом деле все довольно просто:
Создает, записывает в fsyncs и закрывает временный файл, содержащий данные.
Переименовывает временный файл поверх файла, в который вы записываете.
Сбрасывает разрешения для окончательного файла.
В целом это довольно стандартное безопасное сохранение в стиле UNIX. Но вопрос в том, как это влияет на целостность данных? Ключевым моментом, который стоит отметить, является то, что fsync не гарантирует передачу всех данных на диск перед возвратом. Эта проблема имеет длинную и сложную историю, но в итоге можно сказать, что fsync вызывается слишком много раз, в слишком многих местах, чувствительных к производительности, чтобы дать такую гарантию. Это означает, что все возможные проблемы с повреждением файлов возможны, как описано ниже:
o «iProcrastinate_Bad_2.ipr» и «iProcrastinate_Bad_3.ipr» просто содержат неверные данные. Это может произойти следующим образом:
Приложение создает временный файл.
Приложение записывает во временный файл. В ответ на это ядро:
а. выделяет набор блоков на диске
б. добавляет их в файл
с. увеличивает длину файла
д. копирует данные, записанные в буферный кеш
Приложение fsyncs и закрывает файл. Ядро отвечает, планируя блоки данных, которые будут записаны как можно скорее.
Приложение переименовывает временный файл поверх реального файла.
Паника ядра системы.
Когда система перезагружается, изменения из шагов 1, 2a..2c, 3 и 4 восстанавливаются из журнала, что означает, что у вас есть действительный файл, содержащий недопустимые данные.
o "iProcrastinate_Bad_1.ipr" - это лишь небольшое изменение из вышеперечисленного. Если вы откроете файл с помощью шестнадцатеричного редактора, вы обнаружите, что он выглядит хорошо, за исключением диапазона данных со смещением 0x6000..0x61ff, который, кажется, содержит данные, совершенно не связанные с вашим приложением. Следует отметить, что длина этих данных, 0x200 байт, составляет ровно один блок диска. Похоже, ядру удалось записать все пользовательские данные на диск, кроме этого одного блока.
Так, где это тебя оставляет? Маловероятно, что - [NSData writeToFile: options: error:] когда-либо станет более устойчивым, чем уже есть; как я упоминал ранее, подобные изменения имеют тенденцию оказывать негативное влияние на общую производительность системы. Это означает, что ваше приложение должно решить эту проблему.
В этом отношении существует три основных способа укрепить ваше приложение:
A. F_FULLFSYNC - Вы можете зафиксировать файл в постоянном хранилище, вызвав его с помощью селектора F_FULLFSYNC. Вы можете использовать это в своем приложении, заменив - [NSData writeToFile: options: error:] на свой собственный код, который называется F_FULLFSYNC вместо fsync.
Наиболее очевидным недостатком этого подхода является то, что F_FULLFSYNC очень медленный.
B. Журналирование - еще один вариант для принятия более надежного формата файлов, который, возможно, поддерживает журналирование. Хорошим примером этого является SQLite, который вы можете использовать напрямую или через Core Data.
С. безопасное сохранение - наконец, вы можете реализовать более безопасный механизм сохранения с помощью файла резервной копии. Перед вызовом - [NSData writeToFile: options: error:] для записи вашего файла, вы можете переименовать предыдущий файл в другое имя и оставить этот файл на всякий случай. Если при открытии основного файла вы обнаружите, что он поврежден, вы автоматически вернетесь к резервной копии.
Из этих подходов я предпочитаю B, а именно B с Core Data, потому что Core Data предлагает множество преимуществ, выходящих за рамки целостности данных. Тем не менее, для быстрого исправления вариант C, вероятно, является лучшим выбором.
Дайте мне знать, если у вас есть какие-либо вопросы по этому поводу.