Is NSTimer expected to fire when app is backgrounded? - ios

I don't understand it at all but NSTimer in my app definitely is running in background. I have a NSLog in method run by the timer and it is logging while it's in background. It's on iPhone 4 with iOS 4.2.1. I have declared location background support in Info.plist.
I read the docs and many discussions here and elsewhere and it shouldn't be possible. Is it an iOS bug? Or undocumented feature? I don't want to use it and find out in near future, for example with coming of iOS 4.3 that Apple silently "fixed" it and the app won't be working.
Does anybody know more about it?

NSTimer is going to fire whenever the main runloop is running. Apple makes no promises that I know of to unschedule timers or to prevent the main runloop from running. It's your responsibility to unschedule your timers and release resources when you move to the background. Apple isn't going to do it for you. They may, however, kill you for running when you are not supposed to or using too many seconds.
There are many holes in the system that will allow an app to run when it isn't authorized to. It would be very expensive for the OS to prevent this. But you cannot rely on it.

You can have a timer fire while in background execution mode. There are a couple of tricks:
You need to opt into background execution with beginBackgroundTaskWithExpirationHandler.
If you create the NSTimer on a background thread, you need to add it to the mainRunLoop manually.
- (void)viewDidLoad
{
// Avoid a retain cycle
__weak ViewController * weakSelf = self;
// Declare the start of a background task
// If you do not do this then the mainRunLoop will stop
// firing when the application enters the background
self.backgroundTaskIdentifier =
[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundIdentifier];
}];
// Make sure you end the background task when you no longer need background execution:
// [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Since we are not on the main run loop this will NOT work:
[NSTimer scheduledTimerWithTimeInterval:0.5
target:self
selector:#selector(timerDidFire:)
userInfo:nil
repeats:YES];
// This is because the |scheduledTimerWithTimeInterval| uses
// [NSRunLoop currentRunLoop] which will return a new background run loop
// which will not be currently running.
// Instead do this:
NSTimer * timer =
[NSTimer timerWithTimeInterval:0.5
target:weakSelf
selector:#selector(timerDidFire:)
userInfo:nil
repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer
forMode:NSDefaultRunLoopMode];
// or use |NSRunLoopCommonModes| if you want the timer to fire while scrolling
});
}
- (void) timerDidFire:(NSTimer *)timer
{
// This method might be called when the application is in the background.
// Ensure you do not do anything that will trigger the GPU (e.g. animations)
// See: http://developer.apple.com/library/ios/DOCUMENTATION/iPhone/Conceptual/iPhoneOSProgrammingGuide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html#//apple_ref/doc/uid/TP40007072-CH4-SW47
NSLog(#"Timer did fire");
}
Notes
Apps only get ~ 10 mins of background execution - after this the timer will stop firing.
As of iOS 7 when the device is locked it will suspend the foreground app almost instantly. The timer will not fire after an iOS 7 app is locked.

Related

How to run NSTimer in background Thread

I would like to run NSTimer using BackGround thread for that I have written below code but my NSTimer method is not get calling!
can some one help me please
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSOperationQueue* operationQueue = [[NSOperationQueue alloc] init];
[operationQueue addOperationWithBlock:^{
// Perform long-running tasks without blocking main thread
[NSTimer scheduledTimerWithTimeInterval:2
target:self
selector:#selector(targetMethod)
userInfo:nil
repeats:YES];
}];
}
-(void)targetMethod{
NSLog(#"Timer Called");
}
You can use GCD dispatch queue for BackGround thread :=
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void)
{
NSTimer *timer = [NSTimer timerWithTimeInterval:0.5
target:self
selector:#selector(timerFired)
userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
dispatch_async(dispatch_get_main_queue(), ^(void)
{
});
});
Krish, you seem to be getting yourself on a wrong path here.
First, you should create timers while on the main thread by just calling scheduledTimerWithTimeInterval. Putting the call scheduledTimerWithTimerInterval onto an operationQueue is pointless. Timers are called from their thread's runloop. Trying to dispatch them with a different operationQueue will get you into trouble.
If you want the actual operation of a timer to happen in the background, then use dispatch_async (...) in your timer callback method to run code in the background.
Unless you have a legitimate reason to do otherwise, schedule your timer on the main thread.
Your callback method should have a parameter for the timer. That way your callback method can manipulate the timer, for example by invalidating it. scheduledTimer... also returns the timer. Usually you would store that timer object so that you can invalidate it when your view disappears. If you don't do that, you will get a new NSTimer every time viewDidLoad is called. That is you will have more and more timer callbacks running as your app is running.
Learn GCD (grand central dispatch). It is much, much simpler than operation queues. You shouldn't use operation queues unless you have a real good reason.
When you ask "how do we stop the background thread" - you don't. You dispatch code to it, the code runs, and as long as there is code dispatched, it will run. If there is no code dispatched the the background, it stops running until you dispatch code again. That's how it is supposed to work.
If you meant "how do I stop the timer" - that's what invalidate does.
PS. If you wanted to schedule a timer from a background thread (which you don't want, believe me), a correct answer can be found here: iOS5 ARC is it safe to schedule NSTimers from background selectors?
An NSTimer is not actually running on a thread. Scheduling a timer to the main thread (actually its NSRunLoop) will still allow it to handle events and perform other operations before the timer fires.
When the timer fires, the main thread NSRunLoop will invoke the target+selector and then continue waiting for the next event.
I would suggest you replace your code with:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[NSTimer scheduledTimerWithTimeInterval:2
target:self
selector:#selector(targetMethod)
userInfo:nil
repeats:YES];
}
-(void)targetMethod{
NSLog(#"Timer Called");
}
Note: If targetMethod needs to perform expensive operations, it is recommended that it schedule them to a background thread.
The reason your timer never fires, is that the background thread used by your NSOperationQueue probably doesn't have an NSRunloop. NSTimer instances schedule themselves on the NSRunloop of the thread on which they are created/initialized.
B.T.W if you ever need to stop (invalidate) an NSTimer - you must do this on the same thread where you created it.
The main thread has its NSRunloop up and running in advance, since all UI (and UI event handling) is done on that runloop. That's why it's easiest to use NSTimer on the main thread.
A possible solution, is to create an NSRunloop for the background thread you're going to use, e.g. by calling:
[NSRunloop currentRunLoop];
within the block-operation you add to the NSOperationQueue. Documentation says that if there is no runloop for the current thread, currentRunLoop will create one and return it when this is called.
Now another possible issue is, that NSOperationQueue (especially the way you created and initialized it) makes use of undetermined number of threads, can create and dispose threads along the way, and in any way doesn't commit to their lifetime. So... my solution may break.
Instead of using NSOperationQueue, you should create a background thread, create and start its NSRunloop, and then schedule your timer on that runloop.
Another possible solution was already mentioned here - using dispatch_source timer instead of NSTimer, and then - there are better answers than this already.

Running an already running NStimer in Background

In my iOS application I am using NSTimer to to record some events happening in my app. Now I want to keep them recording when my app goes to the background so that even if my app is in the background, my NSTimer will continue to record the events.
Can somebody guide me how I can achieve this behaviour?
Unless you enable one of the Background modes, it is not gonna work.
Why?
You have around 10 minutes of background execution after this the timer is stopped by ios.
The timer will not fire after app is locked (iOS7), since ios suspends the foreground app and bgTask will not get fire again.
There is some workarounds, consider to check below
// NSTimer run when app in background
[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
loop = [NSTimer scheduledTimerWithTimeInterval:0.25 target:self selector:#selector(Update) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:loop forMode:NSRunLoopCommonModes];
Anotherway check this NSTimer in Background
Timers can not run in background. The only thing you can do is, record the time when application goes into background and when application comes into foreground. Take the difference and do the appropriate action.

Make the ios app working in background

I'm using the pitch detection code from demetri miller demetri miller pitch detection in my application. I want the microphone to work in background and give a UILocalNotification on a particular pitch.
How to make the application run in background.
Currently when the app is in background is there way to make it work fully.
Use the NSTimer when app is going from foreground to background start NSTimer and every one second your timer method will call and you can check the application state. you can execute every thing fine.
NSTimer *timerBackground = [NSTimer timerWithTimeInterval:1.0
target:self
selector:#selector(YourMethod:)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timerBackground forMode:NSDefaultRunLoopMode];

NSTimer behavior on background/foreground

Scenario is:
Application has on main run loop that runs every 5 min. When app is backgrounded, the timer is not invalidated.
On foreground after waiting a bit (say 10 min), the timer immediately fires twice. I'm unclear as to what the defined behavior here should be (if any) other than on background, the timer is paused/suspended along with the app.
It seems to me that when the app is foregrounded, the timer actually looks at the elapsed time since background, determines the number of times it should have fired in between, and fires them all. I observed this scenario happening both on iOS 7 simulator and iOS 7 iphone.
Google search didn't turn up much info on this topic. Any ideas/pointers?
Update: Clarification - my question is what is the behavior for NSTimer when the application is backgrounded and then foregrounded again, assuming the timer is not invalidated/nil-ed on background, and a new timer is not created on foreground.
Code sample (code is bit old - pre ARC days):
#implementation ReportHandler {
NSTimer *_reportTimer;
}
- (id)init
{
if (_reportTimer == nil) {
_reportTimer = [[NSTimer timerWithTimeInterval:5*60 target:self selector:#selector(didFireReportTimer:) userInfo:nil repeats:YES] retain];
[[NSRunLoop mainRunLoop] addTimer:_reportTimer forMode:NSDefaultRunLoopMode];
}
}
- (void)didFireReportTimer:(NSTimer *)timer {
// send report over network here, timer is not invalidated here
}
There are no background/foreground handlers either here or in the app delegate dealing with this timer.
It seems to me that when the app is foregrounded, the timer actually looks at the elapsed time since background, determines the number of times it should have fired in between, and fires them all. I observed this scenario happening both on iOS 7 simulator and iOS 7 iphone.
That is a correct description of the behavior of NSTimer and the run loop. When your app is suspended it won't fire (by default, when you background it; but if you start a background task, it will fire as normal while the task is running).

NSTimer not stopping after the app goes into background

Until yesterday I was sure, the NSTimer will get stopped after the app goes into background. I'm have a feeling like experiencing some anomally.
My app has update location and play audio background modes. Update location is refreshed every few seconds. But it only happends on one of the app screens. There also is NSTimer refreshing some UI.
I've scheduled it like this:
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(updateTime) userInfo:nil repeats:YES];
There is also a method, which content is irrelevant now:
-(void)updateTime
{
//irrelevant content, but the method gets fired even when the app is in background
}
The weird thing is, thah the method, which is only fired by the NSTimer and nowehere else is fired even after the ap go into the background. What is happening here? Is that normal behaviour?
Because you are using background modes with location and audio your app is still alive in background.and so your timers are running.
If you remove background modes with location and audio that you are using and then try the timers wont work.
Its Normal behaviour. Correct me if i'm wrong.
Create your timer as public i mean add it in .h file and access it when your app. enter in backGround Mode
- (void)applicationDidEnterBackground:(UIApplication *)application
By above method and set your timer as inValidate.. its fix.
And if you want to do again start your timer then you can access it by
- (void)applicationWillEnterForeground:(UIApplication *)application
this method. Here you need to recreate your timer.
You can stop the timer using invalidate the timer.
[self.timer invalidate];
self.timer= nil;

Resources