NSOperationQueue waitUntilAllOperationsAreFinished not working while in background - objective-c

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

Related

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

Cancel background task when app enters foreground

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.

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

How do I make my App run an NSTimer in the background?

I'm making a benchmark App for test purposes ONLY. I am not intending this to go to the App Store.
What I need is my NSTimer to continue running on the background using a UIBackgroundTaskIdentifier, save data to a Core Data db and finally push the data to a server (I'm using Parse), after a certain time interval, of course.
So basically, I haven´t found any questions which apply to my specific case. I set my NSTimer like so:
UIBackgroundTaskIdentifier bgTask;
UIApplication *app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
}];
self.timer = [NSTimer scheduledTimerWithTimeInterval:self.localInterval target:self selector:#selector(updateCoreData:) userInfo:nil repeats:YES];
the method updateCoreData simply calls the Core Data class and makes the necessary insertions.
I've read about VoIP and the Music playing part, but don't know exactly which one would apply best for my case, nor how to implement them.
I figured it out by myself, for anyone else with a similar problem, what I did was to first turn on the location update flag on your Info.plist file. To do this, you must add the Key called "Required Background Modes" on Xcode 4, then as a value, select "App registers for location updates"
I have a timer declared like so in my .h file:
NSTimer *silenceTimer;
#property (nonatomic, retain) NSTimer *silenceTimer;
Then on the .m file, I declared it like so:
UIBackgroundTaskIdentifier bgTask;
UIApplication *app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
}];
self.silenceTimer = [NSTimer scheduledTimerWithTimeInterval:300 target:self
selector:#selector(startLocationServices) userInfo:nil repeats:YES];
Finally, on the selector method, I made the following:
-(void)startLocationServices {
[locationManager startUpdatingLocation];
[locationManager stopUpdatingLocation];
}
This will create a timer that starts and immediately stops location services after 5 minutes. This will be enough for the app to stay alive indefinately, unless you kill the process.
XCode 6.3.1, for iOS 8.3
There are big diffrences that I've encountered between the apps. behavior while plugged in to your Mac running XCode using debugger and the same apps. behavior while unplugged.
At current iOS version, 8.3, at most your app is allotted is 180 seconds to run in the background for finite long running task with an expiration handler.
No matter what you try, with your phone unplugged your app will be killed by the system in 180 seconds or earlier. I've done a significant amounts of tests and tricks to confirm this. See my post...
My NSTimer Post
Here is a neat way to tell how much time is left for your app to run before termination, keep in mind that you are not guaranteed this time.
NSTimeInterval backgroundTimeRemaining = [[UIApplication sharedApplication] backgroundTimeRemaining];
if (backgroundTimeRemaining == DBL_MAX)
{
NSLog(#"background time remaining = undetermined");
}
else
{
NSLog(#"background time remaining = %0.2f sec.", backgroundTimeRemaining);
}
Hope this helps your investigation. Cheers!

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