NSOperationQueue and background support - ios

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];.

Related

backgroundTask not finishing in iOS

I have the following code which makes a call to the server, before the app is about to exit. My problem is, that the code sometimes works, sometimes not. Mostly not. Here is the code:
//Set the user as in-active if app is force closed
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
NSLog(#"called");
bgTask = [application beginBackgroundTaskWithName:#"setInActive" expirationHandler:^{
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"disp called");
//If the app is about to exit make the user inactive
[[APIManager sharedInstance] setInActiveOnCompletion:^(BOOL finished){
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
});
}
The first NSLog gets called everytime. However the second one does not. It seems as if the app does not even go into the dispatch_async method.
EDIT: So basically all I need to do is tell the server that a user has force quit the app, while that user was signed in. How could I do this?
Any ideas?
applicationWillTerminate is the last method that get called before app is being terminated!! So, you can't do anything after that, if you want to perform something in background then begin that task when you performing your operation, you can't begin background task from applicationWillTerminate because your app will not be in background after this method call!!
When your application is in the background, it is only allowed very little amount of processing, therefore using dispatch_async will not buy you much extra time. Also, Apple does not want that.

BLE background operation issue

I am trying to transfer a firmware file from my app to a wearable hardware.
it takes about some time and when my app goes in background or the lock button is pressed the firmware transfer process discontinues.
ideally it should continue to transfer the firmware. I am using this method to continue the process in background and also also have declared the support for the background modes.
- (void)applicationDidEnterBackground:(UIApplication *)application {
bgTask = 0;
bgTask = [application beginBackgroundTaskWithName:#"MyTask" expirationHandler:^{
// Clean up any unfinished task business by marking where you
// stopped or ending the task outright.
//[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Do the work associated with the task, preferably in chunks.
//[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
This method is not able to do the task.
However if I use this method in Appdelegate - didFinishLaunchingWithOptions it works.
But there is a trade off with putting this piece of code in the above method i.e If I am not transferring the firmware and the app goes in background then after 3 mins this piece of code removes the app and bluetooth connection breaks.
If I am not using this method at all, then the connection remains until it is broken manually but background transfer does not happen.
I have to keep both the operations simultaneously. Please suggest something as I have been for many days on this particular problem.
Thanks in advance.
Try to run your application using BackgroundModes
and remove all the codes from didEnterInbackground

Using multithread to save data on 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.
});
}

Stop Camera recording and save file when entering background

I've been looking for the answer for 2 days, but I can't seem to get it right...
I have an example app that records audio and video from the device using a AVCaptureSession and a AVCaptureMovieFileOutput.
When I start recording I call:
[self.movieFileOutput startRecordingToOutputFileURL:outputURL recordingDelegate:self];
And it starts recording to the file. If I press the button again it stops recording
[self.movieFileOutput stopRecording];
Everything works well, but when I enter background (incoming call or HOME press) I get an error in the delegate method: didFinishRecordingToOutputFileAtURL
My desired action should be saving / completing the file on entering the background. If I call stopRecording on "applicationDidEnterBackground" it will enter background before the applicationDidEnterBackground is called. On entering the active state it is called.... and generates an error and leaves a corrupted movie file...
It seems that it hasn't got enough time to save the file.
What am I missing here?
This is my Error
Error Domain=AVFoundationErrorDomain Code=-11818 "Recording Stopped" UserInfo=0x17594e20 {NSLocalizedRecoverySuggestion=Stop any other actions using the recording device and try again., NSUnderlyingError=0x175d3500 "The operation couldn’t be completed. (OSStatus error -16133.)", NSLocalizedDescription=Recording Stopped}
AVErrorSessionWasInterrupted = -11818
NSOperationQueue is the recommended way to perform multi-threaded tasks to avoid blocking the main thread. Background thread is used for tasks that you want to perform while your application is inactive, like GPS indications or Audio streaming.
If your application is running in foreground, you don't need background thread at all.
For simple tasks, you can add a operation to a queue using a block:
NSOperationQueue* operationQueue = [[NSOperationQueue alloc] init];
[operationQueue addOperationWithBlock:^{
// Perform long-running tasks without blocking main thread
}];
More info about NSOperationQueue and how to use it.
- (void)applicationWillResignActive:(UIApplication *)application {
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
// Wait until the pending operations finish
[operationQueue waitUntilAllOperationsAreFinished];
[application endBackgroundTask: bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
}
You can handle saving in applicationWillResignActive: and then you can continue your process in background.

Understanding background task execution syntax and GCD

I'm trying to fully understand the below code I put together after researching background tasks in iOS and am hoping for some help,
I understand the basic concept,
First we get the app singleton, then we create a block and register with the system the background task, and then finally we asynchronously dispatch the task to run.
So here are the pieces I'm looking for help with:
When background_task is assigned the block, the actual block does not have the code we want run inside it, only the cleanup code in it's completion handler, why is that?
I understand dispatch_async basically starts a new thread and starts working through the code in the block, but where in this dispatch_async request is the background_task referenced? I don't see how the system understands that the code we want executed in the dispatch_async request is related to the background_task we registered earlier.
Why do we need the cleanup code both at the end of the dispatch_async block and in the completion handler of the background_task?
Sorry if these are stupid questions, but I just don't get the syntax,
Here is the code i've cobbled together:
UIApplication *application = [UIApplication sharedApplication]; //Get the shared application instance
__block UIBackgroundTaskIdentifier background_task; //Create a task object
background_task = [application beginBackgroundTaskWithExpirationHandler: ^ { //Register background_task
[application endBackgroundTask: background_task]; //Tell the system that we are done with the tasks
background_task = UIBackgroundTaskInvalid; //Set the task to be invalid
//Above code called when endBackgroundTask is called
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Perform your tasks that your application requires
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(updateText) userInfo:nil repeats:YES];
NSLog(#"\n\nRunning in the background!\n\n");
[application endBackgroundTask: background_task]; //End the task so the system knows that you are done with what you need to perform
background_task = UIBackgroundTaskInvalid; //Invalidate the background_task
});
There is no relationship between the background task identifier and the work you do on a secondary thread. The background task represents a request for some extra time to run. That's all. You need to end it to tell the OS that you've completed the work you wanted to do. If you fail to do that in the time available, your app will be terminated. The task ID is just a token representing the permission from the OS to keep working for a bit of time.
You have to clean up in both places, the expiration handler and the end of your async-dispatched block, because they represent two different occurrences. In the block you dispatch to the concurrent queue, you end the task because you've completed your work in time and you want to let the OS know so it can suspend your app; it doesn't need to terminate it. In the expiration handler, that's your last chance to end the task to prevent your app from being terminated. You haven't completed your work, but you've run out of time. If you didn't end the background task at that point, the OS would kill your app.
By the way, scheduling a timer in a task running on a dispatch queue won't work. A timer is scheduled on a thread's run loop. The worker threads which service dispatch queues can be terminated at any time and, in any case, don't run their run loop.
It seem quite long time since this question was asked and answered.
I just would like to make a note here as I have a feeling that the user Woodstock (and may be many of us) a bit confused about "background thread" and "background task".
The "background thread" is considering in the context of UI thread and background thread.
The "background task" is considering in the context of the task allowable to take longer than normal duration (10 seconds in iOS).
The method beginBackgroundTaskWithExpirationHandler: tells iOS that you need more time to complete whatever you’re doing in case the app is backgrounded (please refer to app's life cycle concept). After this call, if your app is backgrounded it will still get CPU time until you call endBackgroundTask: or system signals it's going to be expired.
Just for clarity I'm setting up the background task as follows:
#interface myClass ()
#property (nonatomic, assign) UIBackgroundTaskIdentifier backgroundUpdateTask;
#end
- (void)importantStuffToCompleteInBackground
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
[self beginBackgroundUpdateTask];
// do important background stuff
[self endBackgroundUpdateTask];
});
}
- (void)beginBackgroundUpdateTask
{
self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endBackgroundUpdateTask];
}];
}
- (void)endBackgroundUpdateTask
{
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundUpdateTask];
self.backgroundUpdateTask = UIBackgroundTaskInvalid;
}
According to beginBackgroundTaskWithExpirationHandler: docs the block will be called only shortly before the remaining background time reaches 0 in case you didn't complete the important background stuff block of code in time by calling the endBackgroundTask in the dispatch_async.
You might have all kinds of long running background tasks running in your app at any one time. There are several ways you can run a background task.
You could spawn a thread or you could use Grand Central Dispatch to push a block onto a worker queue which might look as simple as..
dispatch_async( queue, ^{
[self saveAllUserDataToServer];
} );
but don't be fooled, anything involving multithreading is difficult and dangerous. It's a big subject and it's not clear from your question if you have a specific task to do that you would like help with.
One such danger is that your app can be stopped by the user or System at any point. A background task you have already started could be half way complete and then just stop. This could be a disaster or just inconvenient.
iOS provides a way to not have these tasks halted abruptly. Anytime you start such a task you must let the system know, and then tell it when the task has finished, like so..
- (void)saveAllUserDataToServer {
UIBackgroundTaskIdentifier bt = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler: ^ {}];
... do the work ...
[application endBackgroundTask: background_task];
}
..now you know it is safe to call -saveAllUserDataToServer on a background thread and it will run to completion even if the app is closed half way through.
The one caveat is that you only have a certain time limit to complete the task.. if you take too long the ExpirationHandler block will be called, and you MUST clean up properly in here. This is way the setup code is more likely to look like..
__block UIBackgroundTaskIdentifier bt = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler: ^ {
.. task didn't complete within time limit.. do additional cleanup
[[UIApplication sharedApplication] endBackgroundTask: background_task];
}];
I was getting the following error when my app went to the background because of a continually running process in the background:
Can't endBackgroundTask: no background task exists with identifier 9, or it may >have already been ended. Break in UIApplicationEndBackgroundTaskError() to debug.
Thankfully I came across hooleyhoop's answer and I was able to get around it with something like this code example:
func run() {
DispatchQueue(label: "ping").async {
let tid = UIApplication.shared.beginBackgroundTask(withName: "ping", expirationHandler: {})
//network code
//save data to database code
UIApplication.shared.endBackgroundTask(tid)
run() //repeat process
}
}
You can also use:
UIApplication.shared.beginBackgroundTask(expirationHandler:{})
if you don't need a nice name when debugging

Resources