Cancel background task when app enters foreground - objective-c

In my app I am syncing my data with the server when a user closed the app (i.e. it enters the background). This process takes about a minute and involves various calls to the server to upload data, so I am choosing to perform it as a background thread.
- (void)applicationDidEnterBackground:(UIApplication *)application {
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[asynchronousAPIController processQueueOrWaitWithIsBackgroundSync:true];
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
This works fine, unless a user re-enters the foreground during this sync (i.e. during that first minute). What I want is for the background sync to terminate completed - stop syncing even if it's midway through it's sync. When the app next goes into the background, it can restart the whole process from scratch, no problem.
I've tried using this:
- (void)applicationWillEnterForeground:(UIApplication *)application {
if (bgTask != UIBackgroundTaskInvalid) {
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
}
But it doesn't stop the syncing - that just keeps going. Almost as if once it's started it just keeps going.
I'm suspicious that it might be because the object controlling the sync is a Singleton - i.e. instead of being a class object which could just be destroyed, it's a singleton and so might be staying alive for that reason. Could that make a difference?
Or am I just doing something wrong?
Any advice, much appreciated.

You will need to do the background work in a NSOperation. I would create a NSBlockOperation and add it to a NSOperationQueue. That way a background thread is spawned automatically. For complex operations create a NSOperation subclass.
Inside the NSOperation code check if the operation is cancelled regularly (for example inside a for loop) and exit the the code block if yes (in the for loop: if ([operation isCanceled]) break;).
In applicationWillEnterForeground just cancel the operation.

Related

Objective c - Save completion block when app enter in background

In my application, the user needs to save some information to my database.
[myUser saveInBackground:^(BOOL success){
if(success){
// Do smthing
}
else{
// Alert the user
}
}];
For now it's working fine and when something wrong happenned during the save I'm able to alert the user only if the application is still open and active.
Let's say that the request to my database lasts about 10 sec (bad network for instance) and the user decides to leave the app, my completion block doesn't get fired.
How can I alert the user that the save failed when he has already left the app ?
Thanks
Take a look at Background Modes. This example is taken from the mentioned documentation:
- (void)yourSavingMethod {
UIApplication *application = [UIApplication sharedApplication];
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
// Unfortunately, timeout..
// Clean up any unfinished task.
// 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), ^{
// Save your data.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
You can call -beginBackgroundTaskWithExpirationHandler: when you really need it. But don't forget to end a task. Explore documentation for more details.

Objective-c Long Running Task Thread Confusion

My app needs to do some internet related operation then pop that view when I send the application to background. And then the root view controller fetches some data and updates the collection view. Do you have any idea how can I solve following issues related to above operations(by the way, I use local notifications to start the process):
1) UI related operations(popping current view controller) seems to fail in the background.
2) When I pop my view to root view controller, Root view has some nsurlconnection which sends data to its delegate. Since long running tasks run in global queue, nsurlconnection seems to fail sending any information to its delegate.
I use the following code for this process:
UIBackgroundTaskIdentifier __block bgTask;
UIApplication *app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self performOperation];
NSLog(#"Operation finished");
});
As matt pointed out, you can't do UI operations like popping the view controller when the app is in the background. When the app comes back to the foreground (e.g. the user taps on the icon again), the pop may take place then (if the app wasn't completely terminated in the interim).
I assume matt's observation answered your second question. If it didn't, please clarify what you mean. But this use of UIBackgroundTaskIdentifier doesn't care whether you used global queues or custom queues or whatever. The only restriction is that some UI operations will not take place, so anything contingent upon, for example, viewDidAppear, won't take place.
As an aside, I wanted to point out that you really want to call endBackgroundTask when the code that is doing the task completes, which your code sample does not appear to do. See the Executing a Finite-Length Task in the Background of the App States and Multitasking chapter of the iOS App Programming Guide.
Thus, you might do something like:
UIApplication *app = [UIApplication sharedApplication];
UIBackgroundTaskIdentifier __block bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
NSLog(#"Operation did not finish!!!");
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self performOperation];
NSLog(#"Operation finished");
if (bgTask != UIBackgroundTaskInvalid) {
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
});

NSOperationQueue waitUntilAllOperationsAreFinished not working while in background

The app I'm working on periodically refreshes it's local data cache from an application server (10+ requests, each of them take a fair amount of time). I'm currently running these requests asynchronously as to not block the UI thread. Since these requests do take a while to process and then load into core data, I'd like to leverage the beginBackgroundTaskWithExpirationHandler and dependent operation behavior of the NSOperationQueue.
After I've added all my requests to the operation queue, I'm using the waitUntilAllOperationsAreFinished to block until all the operations are finished (this isn't on the main thread). The problem I'm seeing in my prototype is that when I run the app and immediately background it (press the home button), the waitUntilAllOperationsAreFinished remains blocked even after all the operations have finished... but as soon as I open the app again, the handler finishes. If I run the app and let it remain in the foreground, everything finishes fine. This behavior doesn't always seem to happen when in my actual app, but with the example code below, it seems to:
#import "ViewController.h"
#interface ViewController ()
#property (assign, nonatomic) UIBackgroundTaskIdentifier task;
#property (strong, nonatomic) NSOperationQueue *queue;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self performSelectorInBackground:#selector(queueItUp) withObject:nil];
}
- (void)queueItUp {
UIApplication *application = [UIApplication sharedApplication];
self.queue = [[NSOperationQueue alloc] init];
self.task = [application beginBackgroundTaskWithExpirationHandler:^{
NSLog(#"Took too long!");
[self.queue cancelAllOperations];
[application endBackgroundTask:self.task];
self.task = UIBackgroundTaskInvalid;
}];
for (int i = 0; i < 5; i++) {
[self.queue addOperationWithBlock:^{
[NSThread sleepForTimeInterval:3];
NSLog(#"Finished operation.");
}];
}
NSLog(#"Waiting until all operations are finished.");
[self.queue waitUntilAllOperationsAreFinished];
[application endBackgroundTask:self.task];
self.task = UIBackgroundTaskInvalid;
NSLog(#"All done :)");
}
#end
What am I doing wrong?
Thanks
Your example code does not block perpetually at any point on iOS 6.1 on an iPhone or the simulator. Put a breakpoint at the line [application endBackgroundTask:self.task];, and you will hit it every time.
The behavior you are seeing is because you are logging while in the background after you've told the application to end the background task. I'm not sure of the exact details, but the logs are queued somehow to be printed to the console upon your application's restoration to the foreground. It may be the case that the thread execution is suspended instead.
If you move your NSLog(#"All done"); up to before your call to endBackgroundTask:, you will see the logged output.
Using your sample code, the operations are completing just fine. It's on NSLog(#"All done :)"); that you are hanging. Your queue still exists and has no pending operations, but you may no longer have an active runloop and the main thread is blocked as you and in the background. Since you have completed your pending background operations, you are blocked. When you resume your application continues where it left off. If you do this:
[[self queue] addOperationWithBlock:^{
NSLog(#"All done :)");
}];
The behavior should be even more obvious. It's doing exactly what you're telling it to do.
You have a bunch of operations queued up, and you have called waitUntilAllOperationsAreFinished. When they finish, you are blocked in the background.
It seems like you are trying to execute something when this group of operations are finished. NSOperation provides the ability to have dependancies and completion blocks that allow you to construct this kind of behavior. You can group operations and set a completion block or operation that runs when your group of operations completes. Some of this is covered in the Concurrency Programming Guide

iOS Background Task Completion Programming

I'm trying to add support for completing a task after exiting the foreground for my iOS App. All the tutorials I saw on the internet point me to writing something like this in the Application Delegate:
- (void)applicationDidEnterBackground:(UIApplication *)application {
if ([[UIDevice currentDevice] isMultitaskingSupported]) {
UIApplication *application = [UIApplication sharedApplication];
__block UIBackgroundTaskIdentifier background_task;
background_task = [application beginBackgroundTaskWithExpirationHandler: ^ {
[application endBackgroundTask: background_task];
background_task = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//DO STUFF IN BACKGROUND HERE
MyTask *task = [[MyTask alloc] initWithURL:#"http://google.com"];
[task setDelegate:self];
[task start];
[application endBackgroundTask: background_task];
background_task = UIBackgroundTaskInvalid;
});
}
}
The task is able to start, but after it starts, the next line gets processed and my app is killed. I am able to tell when MyTask stops due to a delegate call, but how do I change my program so that the background task gets set to invalid after the delegate gets called. Would I move
[application endBackgroundTask: background_task];
background_task = UIBackgroundTaskInvalid;
to the delegate function, or do I need to do something else.
Thanks in advance for your help,
Guvvy
You're already in a background thread per your call to dispatch_async. That method will return immediately, but your block will continue to run.
Your problem seems to be that your start method runs another asynchronous process, so that method returns immediately, and your background task is killed before [task start] is able to do anything meaningful.
If you go the route of killing background_task in your delegate, you'll also have to create an ivar to store background_task so your delegate methods can get to it. That seems a little heavy handed.
Since you're already in a background thread, my suggestion would be for you to refactor [task start] so it's synchronous. That way, all the meaningful work will be performed before the next line is processed and background_task is killed. This seems cleaner, and everything for your background task is nicely wrapped in the block sent to dispatch_async.
You can completely remove following code:
[application endBackgroundTask: background_task];
background_task = UIBackgroundTaskInvalid;
When your task is expired, iOS will call your handlerExpiration method to kill it.
For more information, please visit Background for iOS

iOS: Terminating app in background after a certain period of time

I'm trying to implement a passcode lock feature in my app that lets the user choose how much time must go by before the passcode is required for reentry (similar to the passcode functionality of the OS). So for example the user may be able to select that they want the passcode to be required 5, 10, 20 minutes after exiting the app into the background.
I've tried to deal with presenting a passcode view in different ways, but it is often difficult to figure out the best way to present it, and so I had the idea that perhaps it is best to terminate the app after the time is up, and therefore I would only have to present the passcode screen when the app is launched.
Is this possible to do? I had two thoughts about ways to approach this.
1) Have an NSTimer within the app delegate, start it when the app goes into the background, and then when/if the timer reaches the set number of minutes, then terminate the app? I could see a number of things going wrong with this, for example if the OS terminated the app to free up memory sooner than the timer finished. Although that wouldn't be a huge issue.
2) Set an instance of NSDate when the app goes into the background. Then when the app is being launched, see if this date is more than x minutes ago, and present the passcode entry screen depending on that.
I feel like both of these are a little off. I'm inexperienced with Timers, RunLoops, etc, so any advice is appreciated.
Option 2 seems to be a good solution that we have used with success.
Option 2. Use the ApplicationDelegate Lifecycle methods to drive it.
application:didFinishLaunchingWithOptions:
applicationDidBecomeActive:
applicationWillResignActive:
applicationDidEnterBackground:
applicationWillEnterForeground:
applicationWillTerminate:
applicationDidFinishLaunching:
In the applicationWillResignActive method persist the current timestamp to your UserDefaults, and in the applicationWillEnterForeground check this against the current time and if the passcode interval has passed, present your passcode.
(probably best to clear the timestamp when you are active to minimise the chance of false triggering on receiving calls and SMS etc)
Depending on sensitivity you may want to prepare your views before entering foreground to obscure sensitive data, so they do not return in the unlocked state.
you can follow both for better result. for example use option 2 when app active from didFinishLaunchingWithOptions: and option 1 when application enable from
- (void)applicationDidBecomeActive:(UIApplication *)application or - (void)applicationWillEnterForeground:(UIApplication *)application
option 1-Easiest way is to schedule a NSTimer on the background run-loop. I suggest that the following code is implemented on your application delegate, and that you call setupTimer from applicationWillResignActive:.
- (void)applicationWillResignActive:(UIApplication *)application
{
[self performSelectorInBackground:#selector(setupTimerThread) withObject:nil];
}
-(void)setupTimerThread;
{
  NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  NSTimer* timer = [NSTimer timerWithTimeInterval:10 * 60 target:self selector:#selector(triggerTimer:) userInfo:nil repeats:YES];
  NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
  [runLoop addTimer:timer forModes:NSRunLoopCommonModes];
  [runLoop run];
  [pool release];
}
-(void)triggerTimer:(NSTimer*)timer;
{
  // Do your stuff
}
in appDelegate .h
UIBackgroundTaskIdentifier bgTask;
in appDelegate .m
- (void)applicationDidEnterBackground:(UIApplication *)application
{
UIApplication* app = [UIApplication sharedApplication];
// Request permission to run in the background. Provide an
// expiration handler in case the task runs long.
NSAssert(bgTask == UIBackgroundTaskInvalid, nil);
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
// Synchronize the cleanup call on the main thread in case
// the task actually finishes at around the same time.
dispatch_async(dispatch_get_main_queue(), ^{
if (bgTask != UIBackgroundTaskInvalid)
{
[app 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.
// Synchronize the cleanup call on the main thread in case
// the expiration handler is fired at the same time.
dispatch_async(dispatch_get_main_queue(), ^{
if (bgTask != UIBackgroundTaskInvalid)
{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
});
});
NSLog(#"app entering background");
/*
Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
*/
}
OR you could run the NSTimer on a background thread by with something like this (I am intentionally leaking the thread object):
-(void)startTimerThread;
{
  NSThread* thread = [[NSThread alloc] initWithTarget:self selector:#selector(setupTimerThread) withObject:nil];
  [thread start];
}
try with this above code. we use both options its works fine for us. good luck