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?
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.
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];
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With