Ios записывать на диск в фоновом потоке

В настоящее время я пишу несколько файлов на диск в фоновом потоке, просто позвонив

dispatch_async(my_queue,^{
   [self writeToRoot:filename data:data];
};

- (BOOL)writeToRoot:(NSString *)path data:(NSData *)content
{
    NSString *fullPath = [[self rootPath] stringByAppendingPathComponent:path];

    NSString *pathWithoutFile = [fullPath stringByDeletingLastPathComponent];

    BOOL directoryExists = [[NSFileManager defaultManager] fileExistsAtPath:pathWithoutFile];

    if (!directoryExists) {
        NSError *error = nil;
        [[NSFileManager defaultManager] createDirectoryAtPath:pathWithoutFile
                                  withIntermediateDirectories:YES
                                                   attributes:nil error:&error];
        NSParameterAssert(error == nil);
    }

    return [content writeToFile:fullPath atomically:NO];
}

Я делаю это так, чтобы он не блокировал основной поток. Мой вопрос - как обеспечить безопасность потоков. в то время как эта фоновая операция выполняется, что происходит, когда я пытаюсь читать с диска файл, вызывая:

[NSData dataWithContentsOfFile:fullPath];

Будет ли контент поврежден? ИЛИ будет ли операция записи блокировать файл, и операция чтения будет ждать завершения записи?

Ответ 1

Я бы склонялся к dispatch_sync вашей операции чтения с помощью my_queue для обеспечения безопасности потоков (при условии, что это последовательная очередь). Вы также можете использовать любой из инструментов синхронизации (например, блокировки или директива @synchronized), но при условии, что у вас уже настроена очередь для взаимодействия с файлами, используя что последовательная очередь, вероятно, самая простая.

Этот метод использования очереди для координации взаимодействия с общим ресурсом обсуждается в разделе Исключительный код блокировки в руководстве по программированию Concurrency.


Кстати, если вы сохраняете фоновую очередь (что означает, что операция сохранения, по-видимому, достаточно медленная, чтобы оправдывать ее в фоновом режиме), может быть разумно убедиться, что вы запросите немного времени для завершения операция в случае, если приложение, само, прервано (т.е. пользователь вводит кнопку физического дома, приходит вызов и т.д.), пока выполняется операция сохранения. Вы делаете это, вызывая beginBackgroundTaskWithExpirationHandler перед отправкой операции сохранения и вызывая endBackgroundTask, когда это будет сделано:

UIApplication *application = [UIApplication sharedApplication];

// get background task identifier before you dispatch the save operation to the background

UIBackgroundTaskIdentifier __block task = [application beginBackgroundTaskWithExpirationHandler:^{
    if (task != UIBackgroundTaskInvalid) {
        [application endBackgroundTask:task];
        task = UIBackgroundTaskInvalid;
    }
}];

// now dispatch the save operation

dispatch_async(my_queue, ^{

    // do the save operation here

    // now tell the OS that you're done

    if (task != UIBackgroundTaskInvalid) {
        [application endBackgroundTask:task];
        task = UIBackgroundTaskInvalid;
    }
});

Это обеспечит успешную завершение вашей операции сохранения, даже если приложение будет прервано.

И, как указывает Jsdodgers, вы, вероятно, захотите также выполнить запись атома.

Ответ 2

Как ваш код сейчас, да, будет проблема. Это связано с тем, что вы устанавливаете его не для передачи атомарно:

return [content writeToFile:fullPath atomically:NO];

Что атомически означает, что вместо удаления файла, затем, начиная с записи, он записывает файл в отдельное временное расположение файла. После того, как файл полностью написан, он удаляет старую версию файла (если таковой существует) и переименовывает новый файл в правильное имя. Если передача не завершена, ничего не произойдет, и файл temp следует просто удалить.

Итак, если вы измените атомарно в этой строке на YES, то вызов этих данных вернет старые данные до тех пор, пока сохранение не будет завершено, и в любое время после этого вы получите новые данные.

Чтобы сделать это, вам нужно:

return [content writeToFile:fullPath atomically:YES];