8

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

rmaddy
  • 314,917
  • 42
  • 532
  • 579
Tony Law
  • 293
  • 2
  • 13
  • Maybe you can print something in the handler to see if you're running out of time or if the problem is somewhere else. – Andreas May 15 '15 at 15:49
  • Yeah, I'm running an NSLog when the function is called and it simply stops printing when the device auto-locks. If I manually lock the device, it continues to run for a while but stops eventually. I guess I might have to look at a push notification rather than relying on calling a service from the background. – Tony Law May 15 '15 at 15:53
  • I mean print something in the expiration handler. If it only gives you extra execution time based on when the screen would normally have auto-locked then you will find that out with an NSLog. – Andreas May 15 '15 at 16:03
  • The app is basically an engineer management app. It runs through a service and periodically checks back to the server to get new jobs. The call add details to the notification centre, plays an alert sound and badges the app, but I only really want to do this when the app is in the background to minimise the rounds trips to the server to check for new jobs. If an engineer is actively using the app, then this information is naturally discovered with other tasks so no need to notify them of an update. – Tony Law May 15 '15 at 16:12
  • I wanted this functionality in the app delegate in case I wanted to utilise it whilst the app is active in the future. I did a quick google search as my old notification code from the iOS 7 version wasn't working anymore and this is the solution that came up during the search. – Tony Law May 15 '15 at 16:14
  • 1
    beginBakgroundTaskWithExpirationHandler only permits your app to run for about 3 minutes in the background with recent versions of iOS (with older versions it was about 10 minutes). Therefore with a time interval of 300 seconds you are not going to run. – Gruntcakes May 15 '15 at 16:17
  • 1
    Thanks Rob. Between you and Mr H's comment below I can see where it's gone wrong. – Tony Law May 15 '15 at 16:19

1 Answers1

4

A couple of observations:

  1. 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.

  2. 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.

  3. 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.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Sorry to post again. After removing the code and just running the timer code the timer either doesn't start, or when it does start, when I then bring the app to the front again, it's in a frozen state (I guess because the timer is still taking up the thread). Either way the initial code is the only way I can achieve the desired effect. Looks like the xCode learning curve is steeper than I first thought. Oh well, thank you for your replies in any case. – Tony Law May 15 '15 at 19:16
  • No. I have removed the Background Task and moved the timer into the main queue. This has given me the ability to run a timer for the length of time the app remains in the background which is perfect. However, if I remove the run loop command, the timers cease to run at all and if I keep them in, the app freezes when bought to the foreground. – Tony Law May 15 '15 at 21:06
  • Ah ha! timers were not firing because I'd set timerWithTimeInterval rather than scheduledTimerWithTimeInterval Changed it and it works perfectly now and no freeze when returning to the front. Thanks for all your help Rob! – Tony Law May 15 '15 at 21:24
  • Erm. Nope. Scratch that. It runs in the simulator but on the device no notifications are created until the app is bought back to the front. – Tony Law May 15 '15 at 21:28
  • @TonyLaw Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/77933/discussion-between-rob-and-tony-law). – Rob May 15 '15 at 21:40