Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ios write to disk on background thread

I am currently writing some files to disk in the background thread just by calling

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];
}

I am doing this so as it would not block the main thread. My question is how to ensure thread safety. while this background operation is being carried out, what happens when I try to read from disk the file by calling:

[NSData dataWithContentsOfFile:fullPath];

Will be content be corrupted? OR will the write operation lock the file and the read operation will wait until the writing is completed?

like image 251
wjh Avatar asked Aug 19 '13 02:08

wjh


2 Answers

I'd be inclined to dispatch_sync your read operation to the my_queue to ensure thread safety (assuming that it's a serial queue). You could also use any of the various synchronization tools (such as locks or @synchronized directive), but given that you already have your queue set up for file interaction, using that serial queue is probably easiest.

This technique, of using a queue to coordinate interaction with a shared resource is discussed in the Eliminating Lock-Based Code section of the Concurrency Programming Guide.


By the way, if you're saving in a background queue (which means that the save operation is presumably slow enough to justify doing it in the background) it might be prudent to make sure that you request a little time to complete the operation in case the app, itself, is interrupted (i.e. the user taps the physical home button, a call comes in, etc) while the save operation is in progress. You do this by calling beginBackgroundTaskWithExpirationHandler before you dispatch the save operation, and call endBackgroundTask when it's done:

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;
    }
});

This will ensure that your save operation has a fighting chance to successfully complete, even if the app is interrupted.

And, as Jsdodgers points out, you probably want to perform an atomic write, too.

like image 168
Rob Avatar answered Sep 28 '22 05:09

Rob


As your code is right now, yes there will be a problem. This is because you are setting it to not transfer atomically:

return [content writeToFile:fullPath atomically:NO];

What atomically means is that instead of deleting the file then starting the write, it writes the file to a separate, temporary, file location. Once the file is completely written, it then deletes the old version of the file (if one existed) and renames the new file to the correct name. If the transfer is not completed, nothing will happen and the temp file should just be deleted.

So if you change atomically in that line to YES, then calling to that data will return the old data until the save is completed, and anytime afterward will get you the new data.

So to do this, you would want:

return [content writeToFile:fullPath atomically:YES];
like image 36
Jsdodgers Avatar answered Sep 28 '22 06:09

Jsdodgers