This question already has answers here:
backgroundTimeRemaining returns (35791394 mins)?
(2 answers)
Closed 9 years ago.
I have just entered into iOS programming and am struggling with many things.
I am trying to implement small piece of code to getting current location and send it to server in the background.
When I call beginBackgroundTaskWithExpirationHandler, I found that backgroundTimeRemaining property returns so big number. Look at the log below the code.
if (self.backgroundTask == UIBackgroundTaskInvalid) {
NSLog(#"***** startBackground work");
UIApplication *app = [UIApplication sharedApplication];
self.backgroundTask = [app beginBackgroundTaskWithExpirationHandler:^{
NSLog(#"Background handler called. Not running background tasks anymore.");
[app endBackgroundTask:self.backgroundTask];
self.backgroundTask = UIBackgroundTaskInvalid;
}];
DebugLog(#"====>backgroundTimeRemaining:%.1f seconds", app.backgroundTimeRemaining);
if (timer == nil) {
timer = [NSTimer scheduledTimerWithTimeInterval:10 * 60
target:self
selector: #selector(timerFired:)
userInfo:nil
repeats:YES];
}
}
Log:
2013-11-29 09:23:14.852 JeeneeLocatorService[1666:70b] <JLSViewController.m:(317)> ====>backgroundTimeRemaining:179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0 seconds
It does not look normal and I have read from iOS developer library site iOS allows about 10 minutes even though I call
beginBackgroundTaskWithExpirationHandler to do long time background task.
I am testing with iOS7 simulator in XCode5. Is it true that I can have that much of time for background job. Your answer would be very appreciated.
EDIT:
After getting answer, I move the remaining time display code into the timerFired: method.
- (void)timerFired:(NSTimer *)timer {
DebugLog(#"***** Timer fired *****");
UIApplication *app = [UIApplication sharedApplication];
DebugLog(#"====>backgroundTimeRemaining:%.1f seconds", app.backgroundTimeRemaining);
}
But, it still gives me same time left whenever timerFired: method is called. Does not this work just in simulator?
As your new to ios coding, a little tip:
Hold ALT and click on backgroundTimeRemaining. It tell's you it returns an NSTimeInterval which is a double, so using %f is not the problem.
If you click the bottom blue link of the popup it'll open the docs and you'll see it says that this figure will be very large if the app isn't actually in the background.
So I'm guessing this is your issue, that your app is in the foreground when the NSLog is printed.
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.
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 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;
}
}
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