I have a timer running when the device enters the background as I want to keep a check on a small amount of data in my service. I am using the following code in the applicationDidEnterBackground method in app delegate
UIApplication *app = [UIApplication sharedApplication];
//create new uiBackgroundTask
__block UIBackgroundTaskIdentifier bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
//and create new timer with async call:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//run function methodRunAfterBackground
NSString *server = [variableStore sharedGlobalData].server;
NSLog(#"%#",server);
if([server isEqual:#"_DEV"]){
arrivalsTimer = [NSTimer scheduledTimerWithTimeInterval:30 target:self selector:#selector(getArrivals) userInfo:nil repeats:YES];
}
else {
arrivalsTimer = [NSTimer scheduledTimerWithTimeInterval:300 target:self selector:#selector(getArrivals) userInfo:nil repeats:YES];
}
[[NSRunLoop currentRunLoop] addTimer:arrivalsTimer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
This works absolutely fine, until the device auto-locks and then the timer stops ticking. Any suggestions on how to stop this from happening? The default live time is 5 minutes so the majority of devices will be locked long before this even ticks once.
Thanks
A couple of observations:
As Mr. H points out, beginBackgroundTaskWithExpirationHandler only gives you 30 seconds (previously 3 minutes) in contemporary iOS versions. So an attempt to fire a timer in five minutes won't work.
You can use [[UIApplication sharedApplication] backgroundTimeRemaining] to inquire as to how much time you have left.
When the device locks, the background tasks continue. You should not see the app terminating. If the user manually terminates the app through the "double tap the home button and swipe up on task switcher screen", that will kill the background tasks, but not simply locking the device.
A few comments on timers:
The code is adding timer to background queue. That's generally not necessary. Just because the app is in a background state, you can still continue to use the main run loop for timers and the like.
So, just call scheduledTimerWithTimeInterval from the main thread and you're done. There's no point in using up a GCD worker thread with a run loop unless absolutely necessary (and even then, I might create my own thread and add a run loop to that).
By the way, if it's absolutely necessary to schedule timer on some background dispatch queue, it's probably easier to use dispatch timer instead. It eliminates the run loop requirement entirely.
BTW, it's not appropriate to use scheduledTimerWithTimeInterval with addTimer. You call scheduledTimerWithTimeInterval to create a timer and add it to the current run loop. You use timerWithTimeInterval and then call addTimer if you want to add it to another run loop.
Related
Hey I am developing an app in which i have to make API call every 30 sec, so i created NSTimer for it.
But when my app goes into background timer stops firing after 3-4 minutes. So it works only 3-4 minutes in background,but not after that. How can i modify my code so that timer would not stop.
Here is some of my code.
- (IBAction)didTapStart:(id)sender {
NSLog(#"hey i m in the timer ..%#",[NSDate date]);
[objTimer invalidate];
objTimer=nil;
UIBackgroundTaskIdentifier bgTask = UIBackgroundTaskInvalid;
UIApplication *app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
}];
objTimer = [NSTimer scheduledTimerWithTimeInterval:30.0 target:self
selector:#selector(methodFromTimer) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:objTimer forMode:UITrackingRunLoopMode];
}
-(void)methodFromTimer{
[LOG debug:#"ViewController.m ::methodFromTimer " Message:[NSString stringWithFormat:#"hey i m from timer ....%#",[NSDate date] ]];
NSLog(#"hey i m from timer ....%#",[NSDate date]);
}
I even changed the code with the following:
[[NSRunLoop mainRunLoop] addTimer:objTimer forMode:NSRunLoopCommonModes];
This didn't work either.
Don't create UIBackgroundTaskIdentifier task as local and make it global as below:
Step -1
Step -2
Step -3
Step -4
As local one loose scope and global one won't ,and I created a demo and ran it for sometime with 1 sec repeating timer ,and worked smooth.
Still if u face issue pls let me know.
I ran again demo and here are logs of it running.
So its working fine and more than 3 minutes. Also that 3 minute logic is right but as uibackgroundtask is initiated so it shouldn't let it kill this task of timer.
Edited Part:-
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask]; //Remove this line and it will run as long as timer is running and when app is killed then automatically all vairbles and scopes of it are dumped.
}];
Check it and let me know if it works out or not.
Hey I run ur code and I reached the expirationHandler but after released debug point ,the timer was running smooth.
No, don't do background tasks with NSTimer. It will not work as you might expect. You should be making use of background fetch APIs provided by Apple only. You can set the duration at which you want it to be called in that API. Though usually it is not recommended setting duration of the call you would like to make. Take a look at this apple background programming documentation
Also, to get you started quickly, you can follow this Appcoda tutorial
This worked for me, so I'm adding it to StackOverflow for any future answer seekers.
Add the following utility method to be called before you start your timer. When we call AppDelegate.RestartBackgroundTimer() it will ensure that your app will remain active - even if it's in the background or if the screen is locked. However, this will only ensure that for 3 minutes (as you mentioned):
class AppDelegate: UIResponder, UIApplicationDelegate {
static var backgroundTaskIdentifier: UIBackgroundTaskIdentifier? = nil;
static func RestartBackgroundTimer() {
if (AppDelegate.backgroundTaskIdentifier != nil) {
print("RestartBackgroundTimer: Ended existing background task");
UIApplication.sharedApplication().endBackgroundTask(AppDelegate.backgroundTaskIdentifier!);
AppDelegate.backgroundTaskIdentifier = nil;
}
print("RestartBackgroundTimer: Started new background task");
AppDelegate.backgroundTaskIdentifier = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({
UIApplication.sharedApplication().endBackgroundTask(AppDelegate.backgroundTaskIdentifier!);
AppDelegate.backgroundTaskIdentifier = nil;
})
}
}
Also, when starting your app, ensure the following runs. It will ensure that audio is played even if the app is in the background (and while you're at it, also ensure that your Info.plist contains "Required background modes" and that "App plays audio or streams audio/video using AirPlay" a.k.a. "audio" is in its collection):
import AVFoundation;
// setup audio to not stop in background or when silent
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback);
try AVAudioSession.sharedInstance().setActive(true);
} catch { }
Now, in the class that needs the timer to run more than 3 minutes (if in the background), you need to play a sound when only 30 seconds remains of background time. This will reset the background time remaining to 3 minutes (just create a "Silent.mp3" with e.g. AudaCity and drag & drop it to your XCode project).
To wrap it all up, do something like this:
import AVFoundation
class MyViewController : UIViewController {
var timer : NSTimer!;
override func viewDidLoad() {
// ensure we get background time & start timer
AppDelegate.RestartBackgroundTimer();
self.timer = NSTimer.scheduledTimerWithTimeInterval(0.25, target: self, selector: #selector(MyViewController.timerInterval), userInfo: nil, repeats: true);
}
func timerInterval() {
var bgTimeRemaining = UIApplication.sharedApplication().backgroundTimeRemaining;
print("Timer... " + NSDateComponentsFormatter().stringFromTimeInterval(bgTimeRemaining)!);
if NSInteger(bgTimeRemaining) < 30 {
// 30 seconds of background time remaining, play silent sound!
do {
var audioPlayer = try AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("Silent", ofType: "mp3")!));
audioPlayer.prepareToPlay();
audioPlayer.play();
} catch { }
}
}
}
It is normal behavior.
After iOS7, you got exactly 3 minutes of background time. Before that there was 10 minutes if i remember correctly. To extend that, your app needs to use some special services like location, audio or bluetooth which will keep it "alive" in the background.
Also, even if you use one of these services the "Background app refresh" setting must be enabled on your device for the app.
See this answer for details or the background execution part of the documentatio.
When my app is downloading big file and user switching to the other app, i'm running background task like this:
beginBackgroundTaskWithExpirationHandler:
and then if user opening "app switcher" by double click, screenshot of my app is completely random. Sometimes it's showing view controller that was not even open in the app.
ignoreSnapshotOnNextApplicationLaunch not helping, because it's not working at all.
Apple says: Avoid updating your windows and views here: documentation, but I'm not updating views.
I'm also running timer, to check how much background time is left, and this timer is the cause of my problems. If I'm not creating it, everything is working perfect, but I cannot save download state in Expiration handler - not enough time.
How can i avoid this weird behaviour?
-(void)appDidEnterBackground {
UIApplication *application = [UIApplication sharedApplication];
__block UIBackgroundTaskIdentifier bgTask;
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
if(backgroundTimer == nil || ![backgroundTimer isValid]) {
backgroundTimer = [[NSTimer alloc]
initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:0]
interval:1
target:self
selector:#selector(checkBackgroundTimeRemaining)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:backgroundTimer forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] run];
}
}
- (void)checkBackgroundTimeRemaining {
NSTimeInterval timeRemaining = [[UIApplication sharedApplication] backgroundTimeRemaining];
if(timeRemaining < 5) {
if(backgroundTimer != nil) {
[backgroundTimer invalidate];
backgroundTimer = nil;
}
[downloadTask cancelByProducingResumeData:^(NSData *resumeData) {
[self saveResumeData:resumeData];
}];
}
}
Sometimes it's showing view controller that was not even open in the app.
This sounds really fishy and should never happen. Maybe you can add some code to show what you are doing?
ignoreSnapshotOnNextApplicationLaunch is irrelevant here since it's only used to determine what happens when the user taps on your icon again to open the app.
Did you maybe forget to call endBackgroundTask: when you've finished your background task?
I'm not sure what you intend with the timer? If it is to determine how much time is left for you to execute in the background, use UIApplication's backgroundTimeRemaining instead.
I want to check the user current location in every 20 meters user move.All in active state and background.I know its ok to do it in active state.But i want to know is it ok to do the same thing in background mode in ios.Specially want to know is apple reject this method.But in ios7 i think its ok
Actually i want to do get the currant location and if user move more than 1km call serve API ,if user move every 20m check with local data base pop up some data.
All i want to do it on both active state and background state.
at the moment i m using significant location change and region monitoring.But region monitoring have lower accuracy.I want at leaset 20m accuracy.
That why i try to do above method.I know that method give lot of battery drain. But i want accuracy best.
Any one have idea.
You can get user current location both active state and background state app will not get rejected.
In plist you need to set request for getting user location in background state. Once this is set your location manger delegates will keep on calling (because of this there are many chances to get the battery drain quickly) write your location manager method in one class/appdelegate and reuse them, so that you can easily check condition (when ever user moves it will automatically updates user's location so you can check the distance between two points and if user reaches distention then you can prompt alert)
Hope this will help..
In AppDelegate.m file
- (void)applicationDidEnterBackground:(UIApplication *)application{
UIApplication* app = [UIApplication sharedApplication];
UIApplication *bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSTimer* t = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:#selector(al) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:t forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
}
-(void)al{
yourViewController *navigation=[[yourViewController alloc]initWithNibName:#"yourViewController" bundle:nil];
[navigation.locationManager startMonitoringSignificantLocationChanges];
}
Also try the following link:
here
UIApplication *application = [UIApplication sharedApplication];
UIBackgroundTaskIdentifier background_task = 0;
background_task = [application beginBackgroundTaskWithExpirationHandler: ^{
[application endBackgroundTask:background_task];
}];
SEL selector = NSSelectorFromString(action);
timer = [NSTimer scheduledTimerWithTimeInterval:floatUpdateInterval target:self selector:selector userInfo:nil repeats:YES];
The above code (timer) is still running even after the backgroundTimeRemaining is 0 (zero).
How could this be? Isn't there a 10 min max for the above type of approach ?
Is the above code considered "app store" safe for submission ?
You are creating a timer that is just scheduled to repeat. beginBackgroundTaskWithExpirationHandler will be called when the time is up, this is your chance to stop whatever didn't finish in time.
You should invalidate your timer in beginBackgroundTaskWithExpirationHandler.
I'm currently writing an application which depends on location tracking and sending data about the position to the server. The problem, however, is that it has to run 24/7 and currently I'm experiencing random crashes which occur every 2-3 days. What I have done to make the application run constantly in the background is I put a NSTimer in a beginBackgroundTaskWithExpirationHandler method right iside the applicationDidEnterBackground method. The timer executes each minute and stops/starts the location service.
Here is a sample crash log
The code basically looks like this:
UIApplication *app = [UIApplication sharedApplication];
__block UIBackgroundTaskIdentifier bgTaskId = 0;
bgTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
NSTimer *t = [NSTimer scheduledTimerWithTimeInterval: 1 * 60.0 target: self selector: #selector(onTick) userInfo: nil repeats: YES];
[t fire];
if (bgTaskId != UIBackgroundTaskInvalid){
[app endBackgroundTask: bgTaskId];
bgTaskId = UIBackgroundTaskInvalid;
}
}];
I am using GCDAsyncSockets for connection purposes, each call having a timeout of approximately 30 seconds.
I'm really out of ideas, what might be the reason the crashes occur?
Your timer is probably firing off AFTER the task is invalidated (after [UIApplication sharedApplication].backgroundTimeRemaining gets to 0.
The thing is that you can't make the application run constantly in the background. If you want to execute code every once in a while, your only option is going to be using the background location API, setting that your app is using the location background mode in its plist.
You would be getting the CLLocationManagerDelegate callbacks, and you have some time to do some work when those methods are called.
See the Apple documentation regarding background modes: http://developer.apple.com/library/ios/#DOCUMENTATION/iPhone/Conceptual/iPhoneOSProgrammingGuide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html
And the location-awarness manual: http://developer.apple.com/library/ios/#DOCUMENTATION/UserExperience/Conceptual/LocationAwarenessPG/Introduction/Introduction.html#//apple_ref/doc/uid/TP40009497