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

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!

Related

NStimer I want to call methos in background

I want to make calls in background for every 3 min, so I am using Twilio for that. I am able to make calls for every 3 in foreground, but when I built app on iPhone device in background it is not working. After some time fb session is getting logout..
UIApplication *app1 = [UIApplication sharedApplication];
//create new uiBackgroundTask
__block UIBackgroundTaskIdentifier bgTask1 = [app1 beginBackgroundTaskWithExpirationHandler:^{
[app1 endBackgroundTask:bgTask1];
bgTask1 = UIBackgroundTaskInvalid;
}];
//and create new timer with async call:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//run function methodRunAfterBackground
timerForPhone = [NSTimer scheduledTimerWithTimeInterval:[string integerValue] target:self selector:#selector(methodForMakingCall) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:timerForPhone forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
After you put the app into background you don't have much control on it. OS can kill it according to resource needs. Unlike Android, you need to wake your app in order to do some logic. There are some workarounds like using location manager's significant change mechanism but still there is not a designated API for this. You need to keep this in mind whenever you are doing some background logic on iOS.

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

applicationWillEnterForeground / applicationDidEnterBackground working together

I have a decryption and encryption method in my app. When the app enters the background, a file is encrypted. If the app entered the foreground, the file will be decrypted. Thats the main story. Both parts take some time and a lot of memory. In the background, Im doing something like that.
encryptionTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^(void) {
// Wwe took too long. Stop task.
}];
In the applicationWillEnterForeground, Im doing the following:
if (encryptionTaskId != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:encryptionTaskId];
}
I think that works well. But my problem is when someone clicks the home button during the applicationWillEnterForeground is decrypting my file. Again the applicationDidEnterBackground is starting, but my decrypting is not finished. What is the best way to handle that. Is it possible to also wait until the foreground finished?
Are you able to detect if the file is currently being accessed or being encrypted / decrypted? If so, create a timer to do the encryption / decryption again in a few seconds after the previous method has stopped.
UPDATE:
Please see http://developer.apple.com/library/ios/DOCUMENTATION/iPhone/Conceptual/iPhoneOSProgrammingGuide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html#//apple_ref/doc/uid/TP40007072-CH4-SW28 you should be able to use beginBackgroundTaskWithExpirationHandler:. Which will buy you extra time to finish processing before your app moves to the background.
UPDATE 2:
Make sure encryptionTaskId is an instance variable
- (void)applicationDidEnterBackground:(UIApplication *)application
{
encryptionTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
encryptionTaskId = UIBackgroundTaskInvalid;
}];
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
encryptionTaskId = UIBackgroundTaskInvalid;
}
Then in applicationDidEnterForeground you will want to check if encryptionTaskId is still working and if so create a timer to try again shortly to decode it.

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: 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