Using multithread to save data on iOS - ios

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

Adding multi-threading to an application where data is shared between multiple threads (in this case, the data being created by the user and the data you are saving) is a difficult task to manage.
Instead of creating a thread or trying to save off all the data at once, put data to be saved into an internal "this must be saved off" list, and then work it off one N elements at a time periodically in your main thread.
If you get to the point where the user is leaving the screen or the app, then save off all the work that is left in the queue to the database immediately.
You can create a simple timed event (a few times per second) to do the work, which is a very simple approach.
You can explicit control over how many items you save per update.
You should never have concurrency issues.
You should never have to worry about thread start/stop/termination
issues or mutexes.
To create it:
-(void)viewDidAppear:(BOOL)animated
{
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(saveData) userInfo:nil repeats:YES];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(saveAllRemainingItems) name:#"APP EXITING KEY" object:nil]
}
Your update function:
-(void)saveData
{
if([itemsToSave count] > 0)
{
....save off N items, remove from the list
}
}
-(void)saveAllRemainingItems
{
while([itemsToSave count] > 0)
{
...save first item.
[itemsToSave removeObjectAtIndex:0];
}
}
When you leave:
-(void)viewWillDisappear:(BOOL)animated
{
[self.timer invalidate];
[[NSNotificationCenter defaultCenter] removeObserver:self];
[self saveAllRemainingData];
}
To make sure you handle the "app is closing" situation, in your app delegate:
- (void)applicationWillTerminate:(UIApplication *)application
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"APP EXITING KEY" object:nil];
...OTHER CLEANUP ACTIVITIES
}

You can achive mulithreading in IOS with different ways, like NSThread, Operation Queues and GCD.
GCD is the best approch now a days, it uses block. You can execute a code in different thread like this. You can use this in any method.
void performArchiveData{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Now you are in different thread. You can add your code in here.
});
}

Related

DispatchQueue bound to exact Thread

im developing an app, which uses some framework to draw 3D staff via openGL. This framework requires me to call draw() method from exact the same Thread.
So i created a serial DispatchQueue and started CADisplayLink in it, calling draw() at 60FPS. There are few other methods that i have to call from this exact thread, like start() and stop(). This makes queues perfect solution to me.
As you may know DispathQueue does not guaranteed to execute every task on the same thread. Which is quite stressful for me, as it may break my app.
I don't really like the idea to create NSThread and implement my own queue on it.
Are there any way to bind DispatchQueue to exact Thread? Maybe NSOperationQueue can be bound?
As Apple Documentation says:
When it comes to adding concurrency to an application, dispatch queues provide several advantages over threads. The most direct advantage is the simplicity of the work-queue programming model. With threads, you have to write code both for the work you want to perform and for the creation and management of the threads themselves. Dispatch queues let you focus on the work you actually want to perform without having to worry about the thread creation and management. Instead, the system handles all of the thread creation and management for you. The advantage is that the system is able to manage threads much more efficiently than any single application ever could. The system can scale the number of threads dynamically based on the available resources and current system conditions. In addition, the system is usually able to start running your task more quickly than you could if you created the thread yourself.
In simple words, you either work with dispatch queues, simply creating them and sending work to them, OR you work with NSThreads and NSRunLoops, creating them, setting them up, sending work to them, and possibly stopping them.
In detail:
NSThread / NSRunLoop
Creation:
self.thread = [[NSThread alloc] initWithTarget:self selector:#selector(threadMainRoutine) object:nil];
[self.thread start];
Start / management:
- (void)threadMainRoutine
{
// Set the runLoop variable, to signal this thread is alive
self.runLoop = [NSRunLoop currentRunLoop];
// Add a fake Mach port to the Run Loop, to avoid useless iterations of the main loop when the
// thread is just started (at this time there are no events added to the run loop, so it will
// exit immediately from its run() method)
[self.runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
//--- Thread main loop
while (thread_KeepRunning)
{
// Run the run loop. This function returns immediately if the RunLoop has nothing to do.
// NOTE: THIS STATEMENT:
// [self.runLoop run];
// DOES NOT WORK, although it is equivalent to CFRunLoopRun();
CFRunLoopRun();
}
// Unset the runLoop variable, to signal this thread is about to exit
self.runLoop = nil;
}
Adding work to be performed on it:
[self performSelector:#selector(mySelector:) onThread:myThread withObject:myObject waitUntilDone:YES];
Shutdown:
- (void)stop
{
if (self.thread) {
while (self.thread.isExecuting) {
thread_KeepRunning = NO;
CFRunLoopStop([self.runLoop getCFRunLoop]);
[NSThread sleepForTimeInterval:0.1f];
}
}
self.runLoop = nil;
self.thread = nil;
}
Dispatch Queue
Creation:
dispatch_queue_t myQueue = dispatch_queue_create("My Queue", DISPATCH_QUEUE_SERIAL);
Start:
dispatch_resume(myQueue);
Adding work to be performed on it:
dispatch_async(myQueue, (void)^ {
// put the work into this block
});
Shutdown:
dispatch_suspend(myQueue);
myQueue = nil;
In addition, Apple Documentation says that
Because Grand Central Dispatch manages the relationship between the tasks you provide and the threads on which those tasks run, you should generally avoid calling POSIX thread routines from your task code. If you do need to call them for some reason, you should be very careful about which routines you call
So: if you use dispatch queues, don't mess with threads.

Lengthy core-data delete run in background/foreground mode

I have a VERY lengthy core-data delete task of 92k some-odd records which takes a really long time to run. Initially it was locking up the UI thread so I attempted to stick the call on a background thread. What I noticed, in the simulator, however, is that when I background the app this task stops running.
I'm wondering if there is a quick tweak I can make the the code so my delete will continue running once the application has been backgrounded for the 10 minutes or however long they are allowed to run.
When I start up my class I make a background queue:
backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, nil);
Then when its time to delete my objects
- (void)queueForDelete:(FlightRecording *)flight {
NSError *error = nil;
flight.deleteFlagValue = 1;
[managedObjectContext save:&error];
// Remove flights form Internal table dataStructure
[flightList removeObjectAtIndex:[tmpIndexPath row]];
[[self tableView] deleteRowsAtIndexPaths:#[tmpIndexPath] withRowAnimation:UITableViewRowAnimationFade];
//Queue flight for deletion in the background
dispatch_async(backgroundQueue, ^{
NSLog(#"Adding %# to deletion Queue", flight.getFileName);
[self deleteFlight:flight];
});
[[self tableView] reloadData];
}
This code did make the UI more responsive but it still takes about 5 minutes - 10 minutes (in the simulator) to delete my objects. I would like the user to be able to background the app and have the delete process still running because regardless of background/foreground it does have an impact on the responsiveness of the application.
Do this task within an NSManagedObjectContext -performBlock: call. Do it on a child context with private queue concurrency type. That will give you asynchronous processing.
Don't save your context on each call.
Don't update your tableview (reloadData, delteRowsAtIndexPaths) on each call. This is a Model task. Don't update the UI.
Your use of tmpIndexPath is a code smell. That's another sign of mixing Model tasks with View tasks. Deleting a bunch of objects is purely a Model task. Write the code such that no UI or Controller is assumed to exist.

wait for GCD async to stop before new code

I have a GCD that goes on the background. I have a button that when pressed I want it to load a loading wait screen while the GCD finishes, and then execute the rest of the code on that button. Attached is the sample.
Mine does not work, I basically want to say, wait as long as it takes to finish GCD, and load a waiting message in the meantime, when done continue code.
Thank you
- (IBAction)btnTapped:(id)sender
{
shouldCancel=NO;
dispatch_queue_t existingQueque = dispatch_get_main_queue();//finds the current GCD, the one I created in a different method
dispatch_group_t group =dispatch_group_create();
dispatch_group_async(group, existingQueque, ^
{
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);//does not work, I guess group can't be created here.
[self performSelectorOnMainThread:#selector(showWaitViewWithMessage:) withObject:#"Loading" waitUntilDone:YES];//load this until GCD queque done
[self performSelector:#selector(performSearch) withObject:nil afterDelay:0];
});
}
A couple of thoughts:
You suggest that dispatch_get_main_queue() "finds the current GCD, the one I created in a different method". No, this just gets the main queue (the one that, if you use it, will block your user interface), not the queue that you created elsewhere through dispatch_create_queue. The dispatch_get_main_queue() just gets the main queue, and while your searching is happening, your UI will be blocked (e.g. UIActivityIndicatorView won't spin, whatever).
If you've dispatched a whole bunch of tasks to a background queue, if you want to wait for all of them to finish, that's when you use dispatch_group_t or dispatch_barrier, but given what you've shown doesn't require that (you have only one dispatched operation), you just don't need to go there. By the way, barriers are not recommended if you're using global queues.
The typical pattern for a single GCD background task is more simple than your question suggests. You (a) update your UI to say "loading" and show a UIActivityIndicatorView or something like that, so the user has a richer UX showing them that the app is working on something; (b) dispatch the search in the background; and (c) when done, dispatch the UI update back to the main queue. Thus, the typical pattern is:
- (IBAction)btnTapped:(id)sender
{
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// or, if you've already created you own background queue just use that here,
// or just create one here. But don't use dispatch_get_main_queue, as that
// won't use a background queue.
//
// dispatch_queue_t backgroundQueue = dispatch_queue_create("org.yourdomain.yourapp.search", NULL);
[self showWaitViewWithMessage:#"Loading"];
dispatch_async(backgroundQueue, ^{
[self performSearch]; // do this in the background
dispatch_async(dispatch_get_main_queue(), ^{
[self updateUiAfterSearch]; // when done, dispatch UI update back to main queue
});
});
// if you created a queue, remember to release it
//
// dispatch_release(backgroundQueue);
}
As an aside, in your performSelectorOnMainThread, I see no reason to waitUntilDone. Don't wait unless there is some compelling reason to do so. As you see above, this construct isn't needed at all, but just a FYI.
By the way, it's important to know that many servers impose limits on how many concurrent requests a given client may make at a time. If it's possible that you might be initiating multiple requests (e.g. the user taps buttons and the server is slow to respond) and this allows them to run concurrently. In this scenario, it's worth pursuing NSOperationQueue, where you can set maxConcurrentOperationCount. If you use the block versions of the NSOperationQueue methods (e.g. addOperationWithBlock rather than GCD's dispatch_async), the code can be structured in the same way, but it let's you constrain the number of background operations.
Also, NSOperationQueue offers the ability to easily establish dependencies between the operations (e.g. a completion NSOperation that is dependent on all of the others finishing). I can outline that, but the code you posted doesn't necessitate that, so I'll spare you that unless you let me know you want to see what that would look like.
you have to save the queue you create, dont create it each time and if you only want one at a time, use a serial queue
#implementation DDAppDelegate {
dispatch_queue_t queue;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[self do];
[self performSelector:#selector(do) withObject:nil afterDelay:1];
}
- (void)do {
if(!queue)
queue = dispatch_queue_create("com.example.MyQueue", NULL);
dispatch_async(queue, ^{
//serialized
NSLog(#"1");
sleep(10);
});
}
#end
if you want a concurrent queue, use a global queue and dispatch_barrier_async
#implementation DDAppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[self do];
[self performSelector:#selector(do) withObject:nil afterDelay:1];
}
- (void)do {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_barrier_async(queue, ^{
//serialized
NSLog(#"1");
sleep(10);
});
}

NSOperationQueue and background support

I have some problems on elaborating a useful strategy to support background for NSOperationQueue class. In particular, I have a bunch of NSOperations that perform the following actions:
Download a file from the web
Parse the file
Import data file in Core Data
The operations are inserted into a serial queue. Once an operation completes, the next can start.
I need to stop (or continue) the operations when the app enters the background. From these discussions ( Does AFNetworking have backgrounding support? and Queue of NSOperations and handling application exit ) I see the best way is to cancel the operations and the use the isCancelled property within each operation. Then, checking the key point of an operation against that property, it allows to roll back the state of the execution (of the running operation) when the app enters background.
Based on Apple template that highlights background support, how can I manage a similar situation? Can I simply cancel the operations or wait the current operation is completed? See comments for details.
- (void)applicationDidEnterBackground:(UIApplication *)application
{
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
// Do I have to call -cancelAllOperations or
// -waitUntilAllOperationsAreFinished or both?
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// What about here?
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
Thank you in advance.
Edit
If NSOperation main method perform the code below, how is it possible to follow the Responding to Cancellation Events pattern?
- (void)main
{
// 1- download
// 2- parse
// 2.1 read file location
// 2.2 load into memory
// 3- import
// 3.1 fetch core data request
// 3.2 if data is not present, insert it (or update)
// 3.3 save data into persistent store coordinator
}
Each method I described contains various steps (non atomic operations, except the download one). So, a cancellation could happen within each of these step (in a not predefined manner). Could I check the isCancelled property before each step? Does this work?
Edit 2 based on Tammo Freese' edit
I understand what do you mean with your edit code. But the thing I'm worried is the following. A cancel request (the user can press the home button) can happen at any point within the main execution, so, if I simply return, the state of the operation would be corrupted. Do I need to clean its state before returning? What do you think?
The problem I described could happen when I use sync operations (operations that are performed in a sync fashion within the same thread they run). For example, if the main is downloading a file (the download is performed through +sendSynchronousRequest:returningResponse:error) and the app is put in background, what could it happen? How to manage such a situation?
// download
if ([self isCancelled])
return;
// downloading here <-- here the app is put in background
Obviously, I think that when the app is then put in foreground, the operation is run again since it has been cancelled. In other words, it is forced to not maintain its state. Am I wrong?
If I understand you correctly, you have a NSOperationQueue and if your application enters the background, you would like to
cancel all operations and
wait until the cancellations are processed.
Normally this should not take too much time, so it should be sufficient to do this:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[_queue cancelAllOperations];
[_queue waitUntilAllOperationsAreFinished];
}
The definition of "too much time" here is approximately five seconds: If you block -applicationDidEnterBackground: longer than that, your app will be terminated and purged from memory.
Let's say that finishing the cancelled operations takes longer than 5 seconds. Then you have to do the waiting in the background (see the comments for explanations):
- (void)applicationDidEnterBackground:(UIApplication *)application
{
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
// If this block is called, our background time of normally 10 minutes
// is almost exceeded. That would mean one of the cancelled operations
// is not finished even 10 minutes after cancellation (!).
// This should not happen.
// What we do anyway is tell iOS that our background task has ended,
// as otherwise our app will be killed instead of suspended.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Normally this one is fast, so we do it outside the asynchronous block.
[_queue cancelAllOperations];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Wait until all the cancelled operations are finished.
[_queue waitUntilAllOperationsAreFinished];
// Dispatch to the main queue if bgTask is not atomic
dispatch_async(dispatch_get_main_queue(), ^{
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
});
}
So basically we tell iOS that we need some time to perform a task, and when the task is finished of the time runs out, we tell iOS that our task has ended.
Edit
To answer the question in your edit: To respond to cancellation, just check for cancellation whenever you can, and return from the -main method. A cancelled operation is not immediately finished, but it is finished when -main returns.
- (void)main
{
// 1- download
if ([self isCancelled]) return;
// 2- parse
// 2.1 read file location
// 2.2 load into memory
while (![self isCancelled] && [self hasNextLineToParse]) {
// ...
}
// 3- import
// 3.1 fetch core data request
if ([self isCancelled]) return;
// 3.2 if data is not present, insert it (or update)
// 3.3 save data into persistent store coordinator
}
If you do not check for the cancelled flag at all in -main, the operation will not react to cancellation, but run until it is finished.
Edit 2
If an operation gets cancelled, nothing happens to it except that the isCancelled flag is set to true. The code above in my original answer waits in the background until the operation has finished (either reacted to the cancellation or simply finished, assuming that it does not take 10 minutes to cancel it).
Of course, when reacting to isCancelled in our operation you have to make sure that you leave the operation in a non-corrupted state, for example, directly after downloading (just ignoring the data), or after writing all data.
You are right, if an operation is cancelled but still running when you switch back to the foreground, that operation will finish the download, and then (if you programmed it like that) react to cancel and basically throw away the downloaded data.
What you could do instead is to not cancel the operations, but wait for them to finish (assuming they take less than 10 minutes). To do that, just delete the line [_queue cancelAllOperations];.

creating UI elements on NSOperationQueue non-main queues causes strange behavior

I had some code that created and added UIView subviews via a queue created by [[NSOperationQueue alloc] init], and it led to strangely laggy behavior. Subviews were only added after abnormally long delays.
But then I switched to using [NSOperationQueue mainQueue] for these parts, and responsiveness turned normal.
I would just like an explanation for the laggy behavior I saw using the first approach.
From Apple doc
Threads and Your User Interface
If your application has a graphical user interface, it is recommended that you receive
user-related events and initiate interface updatesfrom your
application’s main thread. This approach helps avoid synchronization
issues associated with handling user events and drawing window
content. Some frameworks,such as Cocoa, generally require this
behavior, but even for those that do not, keeping this behavior on the
main thread hasthe advantage of simplifying the logic for managing
your user interface. There are a few notable exceptions where it is
advantageous to perform graphical operations from other threads. For
example, the QuickTime API includes a number of operationsthat can be
performed from secondary threads, including opening movie files,
rendering movie files, compressing movie files, and importing and
exporting images. Similarly, in Carbon and Cocoa you can use secondary
threads to create and process images and perform other image-related
calculations. Using secondary threads for these operations can greatly
increase performance. If you are not sure about a particular graphical
operation though, plan on doing it from your main thread
In addition, UI classes are not thread-safe according to the thread programming guide.
So, avoid to update the UI from a thread different from the main thread.
If you run an NSOperation (within a queue) you could update your UI (for example after having download some data required for your app lifecycle) performing a method in the main thread like the following:
-(void)main {
// e.g the delegate could be the controller that has the view that you want to update
if (delegate) {
NSURL *url = [delegate urlForDownloadOperation:self];
if ( nil == url ) return;
self.downloadedImage = [[NSImage alloc] initWithContentsOfURL:url];
// e.g. rather than invoking processImage directly on the delegate, ensure that the method draw the image on the main thread
[delegate performSelectorOnMainThread:#selector(processImage:)
withObject:self waitUntilDone:YES];
}
}
Or you could send a notification to the component that need to update the UI like:
- (void)main {
NSURL *url = [delegate urlForDownloadOperation:self];
if ( nil == url ) return;
self.downloadedImage = [[NSImage alloc] initWithContentsOfURL:url];
// e.g. send a notificatio to inform some components that it is ready to update the UI with some content
[[NSNotificationCenter defaultCenter] postNotificationName:#"importData" object:self];
}
The component that needs to update the UI will register for that notification like
- (void)processImage:(NSNotification*)notification
{
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(processImage:) withObject:notification waitUntilDone:YES];
return;
}
// update the UI here, you are running on the main thread
}

Resources