I'm working on an sdk that uses NSTimer to make a server call when it expires at x mins and the server call resets the timer back to x mins after doing some work. The problem I'm having is that my timer stops running when my app is in the background and resumes immediately when my app is back in the foreground. How do I get this to work?
//initialize self.myTimer somewhere in my code
//called once somewhere in my code-->[self resetTimer:self.myTimer expiry:30];
- (void)resetTimer:(NSTimer *)timer expiry:(float)seconds {
[timer invalidate];
NSTimer *newTimer = [NSTimer timerWithTimeInterval:seconds
target:self
selector:#selector(updateTimer:)
userInfo:nil
repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:newTimer forMode:NSRunLoopCommonModes];
self.myTimer = newTimer;
}
- (void) updateTimer:(NSTimer *)timer {
[timer invalidate];
dispatch_queue_t queue = dispatch_queue_create("update", NULL);
dispatch_async(queue, ^{
[self serverCall];
});
}
-(void)serverCall{
//make server call and do some work
[self resetTimer:self.myTimer expiry:30];
}
The timer won't continue to fire when the app is in the background. If you're willing to let the OS set the pace of the server calls, you can accomplish this by consulting the section "Fetching Small Amounts of Content Opportunistically" in this background execution doc.
The gist is to set your app's UIBackgroundModes key == fetch in info.plist.
When iOS decides to grant your app some cycles, you can run a short task in:
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
[self serverCall]; // BUT! read on
}
But you'll need to refactor your serverCall to tell the caller when its done. Otherwise, you won't know when to invoke the completionHandler:.
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
[self newServerCallWithCompletion:^(BOOL gotNewData) {
UIBackgroundFetchResult result = (gotNewData)? UIBackgroundFetchResultNewData : UIBackgroundFetchResultNoData;
completionHandler(result);
}];
}
See the docs for more options on UIBackgroundFetchResult.
With this, iOS will set the pace of requests, so you won't be able to make them more often than when you're app is given the chance. By persisting the time of the last request, you can make requests less often. Just check if the interval since the last request is <= to some desired max frequency. If it is, just call the completionHandler right away with "no data".
Related
As far i know, there are two distinct stages of iOS application lifecycle when it is hidden:
Background - when we are still able to proceed computation and run code;
Suspended - when any computation is freezed and we can't do anything till the user returns to the app.
So i'm a bit confused about a following example:
- (void)applicationDidEnterBackground:(UIApplication *)application {
(void)(^expirationHandler)() = [^{
[application endBackgroundTask:bgTask];
self.bgTask = UIBackgroundTaskInvalid;
} copy];
self.bgTask = [application beginBackgroundTaskWithName:#"MyTask" expirationHandler:expirationHandler];
[NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(count)
userInfo:nil
repeats:YES];
expirationHandler();
}
- (void)count {
int i = 0;
while (i < 10000000) {
i++;
}
NSLog(#"%d", i);
}
P.S. I've reproduced it from memory, let me know if something wrong, but question is not related to correctness of the snippet.
I tested it on iPhone 6+ with iOS 9.3 and the specified timer persists firing, furthermore count method executes code (you can check it by i value). Am i right that app must proceed to suspended state after the timer scheduled? If so, why is count able to execute code?
Thanks in advance.
As per #Paulw11 in comments,
Are you testing while running under the debugger in Xcode? If so, then you app doesn't have background time limits or get suspended
I haven't found any proofs, but through empirical enquiry it appears to be true.
I'am working on an application in xcode-5, I need to upload some data in background mode, I need a set of code to be repeated till the device terminates the background process. I am able to do it once but I need to repeat again and again so that i can check the connectivity and perform the upload. I doing the same thing when the application comes in active mode.
Currently I'm using Timers with the following code :
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[bgrdTimer_ActiveMode invalidate];
bgrdTimer_ActiveMode=nil;
self.backgroundTask=[application beginBackgroundTaskWithExpirationHandler:^{
self.backgroundTask=UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
bgrdTimer_BackGroundMode=[NSTimer scheduledTimerWithTimeInterval:40.00 target:self selector:#selector(repeatUploadProcess_BackGroundMode) userInfo:nil repeats:YES];
});
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[bgrdTimer_BackGroundMode invalidate];
bgrdTimer_BackGroundMode=nil;
bgrdTimer_ActiveMode=[NSTimer scheduledTimerWithTimeInterval:40.00 target:self selector:#selector(repeatUploadProcess_ActiveMode) userInfo:nil repeats:YES];
}
repeatUploadProcess_ActiveMode and repeatUploadProcess_BackGroundMode are the methods containing the set of code which is suppose to be repeated
the problem is when I invalidate the bgrdTimer_ActiveMode the other timer dosn't gets invoked.
Have you enabled the background fetch mode? Click on the project target, and under the capabilities tab select "background fetch".
Theoretically you should also be setting [application endBackgroundTask:bgTask]. It sounds like you know, but if it never ends, iOS will end up killing your app.
I'm developing a Cordova project using this plugin: Cordova Plugin Background Geolocation
to get GPS updates when my app is in background.
I would like to stop getting updates after 3 minutes the app is on background.
I don't have the Objective-C skills to modify the plugin to achieve this.
I guess that there's a way to use a timer on Objective-C to stop the service after 3 minutes.
Can anyone help me with this?
UPDATE
The plugin has a stop method here.
Try this solution, here you need to create global object of NSTimer and check whether NSTimer object is validate or not
-(void)applicationDidEnterBackground:(UIApplication *)application {
if ([objTimer isValid]) {
[objTimer invalidate];
}else{
objTimer = [NSTimer scheduledTimerWithTimeInterval:180 target:self selector:#selector(stopGPS:) userInfo:nil repeats:NO];
}
objTimer = nil;}
- (void)stopGPS:(NSTimer *)timer {
// code for stoping GPS service.}
You must modify your .plist file to indicate that you will be running tasks once the app has entered background...That being said, what you want might look something like this (all in AppDelegate)
- (void)applicationDidEnterBackground:(UIApplication *)application {
NSLog(#"Entered background");
[self monitorLocation];
}
- (void)monitorLocation
{
[NSTimer scheduledTimerWithTimeInterval:180 //That's in seconds
target:self
selector:#selector(stopMonitoring)
userInfo:nil
repeats:NO];
//Do whatever monitoring here
}
- (void)stopMonitoring
{
//Stop monitoring location
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
NSLog(#"app will enter foreground");
[self stopMonitoring];
}
I am trying to get this to work correctly but it always seems to end early at 174 seconds (2.9 mins). I been following every tutorial possible online on how to use beginBackgroundTaskWithExpirationHandler and I don't see anything wrong with my code. I need this to end at 8.5 mins. The endBackgroundTask method doesn't even gets call before the expiration handler gets called. Is anything wrong with this?
- (void)applicationDidEnterBackground:(UIApplication *)application {
if([[UIDevice currentDevice] respondsToSelector:#selector(isMultitaskingSupported)])
{
NSLog(#"Multitasking Supported");
backgroundTask = [application beginBackgroundTaskWithExpirationHandler:^ {
NSLog(#"Terminated");
//Clean up code. Tell the system that we are done.
[application endBackgroundTask: backgroundTask];
backgroundTask = UIBackgroundTaskInvalid;
}];
timer = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:#selector(count:)
userInfo:nil
repeats:YES];
}
else
{
NSLog(#"Multitasking Not Supported");
}
}
-(void)turnOffDesktops:(NSTimer *)time {
//do smth
if(count < (60*8.5)){
NSLog(#"%d",count);
count++;
}else{
[application endBackgroundTask: backgroundTask];
backgroundTask = UIBackgroundTaskInvalid;
[timer invalidate];
timer = nil;
count = 0;
}
}
There has never been a commitment from Apple on how long you would be allowed to perform your background tasks. Historically (until iOS7), apps were given usually 10 minutes to run in the background. This is no longer the case! Watch the WWDC 2013 video on backgrounding. With the addition of the new download & upload API in NSURLSession (ability to schedule download and upload tasks on an external dedicated daemon), Apple has reduced the allowed background time significantly. They've done this because this API has always been meant for download and upload, not arbitrary tasks in the background.
You can determine the amount of time left in background by querying - [UIApplication backgroundTimeRemaining]. You can use this to schedule your code to start at the latest possible.
I would like to trigger action after some ammount of time (in production that will be 30 minutes) and right now I'm using NSTimers scheduledTimerWithTimeInterval. During tests (with timeout being 20 seconds, not 1800 seconds) everything seems fine. In debbuging mode (running from XCode) everything seems fine as well, because the device does not autolock. But in real life, when application is ran on device, autolocking (precisely autolocking, triggering lock button does not) "freezes" the timer (or at least moves the timer trigger somehow to future).
Can I handle that situation? Of course I can disable idleTimer in UIApplication sharedApplication but when application will enter background mode ipad still can autolock.
Actually you can,
First of all, create a default value for "startTimeOfMyExoticTimer" to NSUserDefaults:
[[[NSUserDefaults] standardDefaults] setObject:[NSDate date] forKey:#"startTimeOfMyExoticTimer"];
Then kick of a timer to check if the valid time range is over:
[NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:#selector(checkIfTimeIsOver) userInfo:nil repeats:YES];
Then implement your checker method:
-(void)checkIfTimeIsOver{
NSDate * now = [NSDate date];
NSDate * startTime = [[NSUserDefaults standardDefaults] objectForKey:#"startTimeOfMyExoticTimer"];
// Here compare your now and startTime objects. (subsract them) and see the difference between them. If your time range is to be set on 30 seconds. Then you should check if the time difference between those objects are bigger than 30 seconds.
}
This will be working even if the device is locked, the app is in background etc.
This approach worked for me, hope it'll work for you too
//Implement this block help your application run background about 10 minutes
#interface AppDeledate(){
UIBackgroundTaskIdentifier backgroundTaskId;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// if missing interval exist > start timer again and set missing interval to 0
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
NSLog(#"Did Enter Background");
backgroundTaskId = [application beginBackgroundTaskWithExpirationHandler:^{
NSLog(#"Background Task Expired !!!\n");
[application endBackgroundTask:backgroundTaskId];
backgroundTaskId = UIBackgroundTaskInvalid;
//save missing interval to NSUserDefault
}];
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
NSLog(#"Will Enter Foreground");
if (backgroundTaskId && backgroundTaskId != UIBackgroundTaskInvalid) {
[application endBackgroundTask:backgroundTaskId];
backgroundTaskId = UIBackgroundTaskInvalid;
}
}