I am developing an iPhone app which keeps some data. I am using archiving method from NSKeyedArchiver
class to save the data to disk. I would like to periodically save the data to disk. The problem is, when the data grows bigger, it takes more time and it actually interrupts the user's current actions.
Hence, I want to use multithreading to solve this problem. From my understanding of multithreading, when I want to save the data to disk, I should create a new thread, run the saving task on the new thread, then terminate the thread. I should also make the thread so that it won't immediately terminate when the app terminates, to finish saving data. This way, the user can continue to interact with the interface.
That being said, I am not familiar with the actual code that does these work...what would the above look like in code?
A couple of thoughts.
You want to use a serial dispatch queue or operation queue.
Note, we probably want it to write to persistent storage serially (if you're saving it to the same filename, for example), i.e. not permit another save to be initiated until the prior save is finished. I suspect that's exceedingly unlikely that your infrequent saves could ever trigger a save while the prior one is still in progress, but as a general principle you should not use concurrent queues unless you write code that supports concurrent operation (which we're not doing here). This means that you do not use the GCD global queues.
For example, to create serial dispatch queue using Grand Central Dispatch (GCD) would be:
@property (nonatomic, strong) dispatch_queue_t queue;
Then instantiate this (e.g. in viewDidLoad
):
self.queue = dispatch_queue_create("com.domain.app.savequeue", 0);
Then use this queue
dispatch_async(self.queue, ^{
// do your saving here
});
For a review of concurrency technologies, see the Concurrency Programming Guide. Both dispatch queues (GCD) and operation queues are solid choices.
You might want to be careful about synchronization issues. What if your app proceeds to start changing the data while the save is in progress? There are a bunch of options here, but the easiest is to copy the data to some temporary object(s) in the main queue before you dispatch the save task to the background queue:
// copy the model data to some temporary object(s)
dispatch_async(self.queue, ^{
// save the temporary object(s) here
});
Or, instead of creating a copy of the model, you can alternatively (and this is a little more complicated if you're not familiar with GCD) use a variation of the "reader-writer" pattern that Apple discusses in WWDC 2012 video Asynchronous Design Patterns with Blocks, GCD, and XPC. Bottom line, you can queue to not only perform asynchronous write to persistent storage, but also to synchronize your updates to your model using a "barrier" (see Using Barriers in the GCD reference):
self.queue = dispatch_queue_create("com.domain.app.modelupdates", DISPATCH_QUEUE_CONCURRENT);
Then, when you want to save to disk, you can do
dispatch_async(self.queue, ^{
// save model to persistent storage
});
But, whenever you want to update your model, you should use barrier so that the updating of the model will not happen concurrently with any read/save tasks:
dispatch_barrier_async(self.queue, ^{
// update model here
});
And, whenever you read from your model, you would:
dispatch_sync(self.queue, ^{
// read from model here
});
Theoretically, if you're worried about the possibility that you could conceivably do your save operations so frequently that one save could still be in progress when you initiate the next one, you might actually employ two queues, one serial queue for the saving operation (point 1, above), and the concurrent queue outlined here for the synchronization process.
Finally, Putz1103 is correct, that if it's possible that the app can be terminated while a save is in progress, you might want to add the code to allow the write to persistent storage to complete:
dispatch_async(self.queue, ^{
UIBackgroundTaskIdentifier __block taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^(void) {
// handle timeout gracefully if you can
[[UIApplication sharedApplication] endBackgroundTask:taskId];
taskId = UIBackgroundTaskInvalid;
}];
// save model to persistent storage
// when done, indicate that the task has ended
if (taskId != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:taskId];
taskId = UIBackgroundTaskInvalid;
}
});
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