Search
Rich's Mad Rants
Powered by Squarespace

Entries in Thread Safe (1)

Monday
Mar042013

Snapshotting Data For Background Saves

Sometimes things are so obvious, I just can't see them.

Last week, while teaching a class, I was trying to convince my students to always save and load data on a background thread. Mostly, I was trying to introduce them to Grand Central Dispatch, and convince them that it was a convenient way to bounce a task onto a background thread, then bounce the results back to the main thread. This--in theory--would allow us to do all our UI changes on the main thread. More importantly, if I only access my classes properties on the main thread, I can safely use nonatomic properties.

After all, atomic properties are slow, and they only handle a limited number of threading issues. Sure, it prevents my getter and setter from clobbering each other--but code that operates at a higher level may still not be thread safe. For example, what happens if I have a method that gets the value from my property, then I modify that value and set it again. Even though the accessors are thread safe, I'm going to have problems if my threaded code interleaves two of these method calls. Best to just keep all access on the main thread.

So, I introduced the following code:


- (void)save {
    
    NSArray *books = [self.books copy];
    
    dispatch_queue_t backgroundQueue = 
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_async(backgroundQueue, ^{
        
        [NSKeyedArchiver
         archiveRootObject:books
         toFile:[[self documentURL] path]];
    });
}

That's when one of my students pointed out that this protected the self.books array, but we could still have threading problems with the individual Book objects. Now, in this case it didn't matter. Our Book objects were immutable (yet another reason to use immutable objects when possible), but how should we handle this in the general case?

Writing code to make a deep copy of the array seemed like the correct answer--but that could rapidly get tedious--especially if you had a complex object graph. In the back of my head, I knew there had to be a simpler answer--I knew that I'd used a simpler solution many times in my own code--but I couldn't put my finger on it. Not until this morning.

Of course, the easiest way to snapshot this data is to break the save up into two steps. First, use the NSKeyedArchiver to serialize it into an NSData object, then save the data object on a background thread. The best part is, this reuses the serialization code we've already written, so it doesn't require any additional code. This is one of those things that is obvious in hind-sight. Still, I'm not sure how many people are aware of it. The code is shown below:



- (void)save {
        
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.books];
    
    dispatch_queue_t backgroundQueue = 
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_async(backgroundQueue, ^{
        [data writeToFile:[[self documentURL] path] atomically:YES];
    });
}

The in-memory operation to create the NSData object should be fast--so there should be no problem doing this on the main thread. Saving to disk can be slow, so we simply move that portion off to the background. Of course, we could run into problems if we have a massive amount of data--since we'll be essentially making two copies of it in memory. However, this would be true even if we implemented our own deep copy. The bottom line is, if we're reading/writing a massive amount of data, we need to look into other solutions (e.g. Core Data or NSFileWrapper).