ios write to disk on background thread

12,258

Solution 1

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.

Solution 2

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];
Share:
12,258

Related videos on Youtube

wjh
Author by

wjh

Updated on May 07, 2020

Comments

  • wjh
    wjh almost 4 years

    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?

  • Jsdodgers
    Jsdodgers over 10 years
    I never said that it guarantees thread safety. Only that it prevents the file from becoming corrupted and makes it very unlikely (although unknown exactly how unlikely) that the OP will get the error that he was asking about. (which is the data object being corrupted). I do not know what would happen if he tries to access the data at the moment its name is being changed (however the method does it). Thus, as you and Apple have pointed out, it is not thread safe.
  • Jsdodgers
    Jsdodgers over 10 years
    Also, what they mean by it is not thread safe is that when multithreading and setting/getting the same object in multiple threads, you cannot guarantee whether the value you get will be the value it had before or after it was set. Atomicity, however, guarantees that whichever you do get, it will be the full, uncorrupted object (which is what the question was asking for).
  • Jsdodgers
    Jsdodgers over 10 years
    I believe that is the reason the initWithFile etc methods of objects have a version that takes an error as a parameter. If the data was changed during the process, it would most likely return nil as the object (which is what the docs say will happen in the case of an error) and explain what happened in the error object that you pass in, rather than giving you corrupt data.
  • wjh
    wjh over 10 years
    This is the solution I ended up using. Writing and Reading from the disk from the same queue. Thanks a lot.

Related