4

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.

Eli Iser
  • 2,788
  • 1
  • 19
  • 29
Vivek Shah
  • 430
  • 5
  • 22
  • 2
    without using one of the background modes you'll get only 3 minutes in the background to finish tasks ( 3 minutes on iOS 7 ), see here: https://developer.apple.com/library/ios/documentation/iphone/conceptual/iphoneosprogrammingguide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html, which background mode is appropriate for your case – Guy S Sep 04 '14 at 06:00

4 Answers4

8

Don't create UIBackgroundTaskIdentifier task as local and make it global as below:

Step -1

Initiating varible

Step -2

ViewDidLoad Method and added barButton

Step -3

BarButton method call

Step -4

Timer method call

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. enter image description here

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.

Highlighted part is when I reached handler

nikhil84
  • 3,235
  • 4
  • 22
  • 43
  • Thanks Nikhil, I tried as per your solution, but it didn't work. My timer again stops after 3 minute. – Vivek Shah Sep 04 '14 at 06:32
  • Yes Nikhil , If i attached my device then run the app then go to background my NSLog line is executing without any problem. I maintain Log file in project.So from "LOG debug" i came to know that when my device is not connected and app is in background , it works for only 3 minutes. If you want to check full source code then let me know. – Vivek Shah Sep 04 '14 at 06:54
  • I came across that stopping of timer that is after a certain time ExpirationHandler is called and app endBackgroundTask:bgTask kills ur timer as backgroundtask is killed. So remove that [app endBackgroundTask:bgTask] line from that handler then give a run. I have made web service calls for a long time without getting stopped (at those point I did across it). – nikhil84 Sep 04 '14 at 07:02
  • Still if not satisfied then porvide ur source code will look into that thoroughly. – nikhil84 Sep 04 '14 at 07:02
  • If i remove "[app endBackgroundTask:bgTask]" line. But after that my timer don't even call single time in background. If you can handle it let me know how can i give u source code. – Vivek Shah Sep 04 '14 at 07:24
  • This "[app endBackgroundTask:bgTask]" line is just to kill ur backgroundtask only(which will also kill scope of ur timer) so it has no connection with your timer starting. I will update my post with step-wise code. – nikhil84 Sep 04 '14 at 08:05
  • Through github. You could upload your code over there and share the link – nikhil84 Sep 04 '14 at 12:17
  • https://github.com/ShahVivek/Demo1 Here is my code ... What i am doing for testing is... 1)Run the app on device 2)Stop the app and then kill it 3)Open the app and press the start button 4)Go to background mode and sleep it 5)Then i check the log file from the iFunbox >>open the app >>Document >>LOG.html And this LOG.html says me that it stops after 3 minute. – Vivek Shah Sep 05 '14 at 10:17
  • It's working fine in simulator and I have uploaded screenshot of same and still I have left the app running. – nikhil84 Sep 05 '14 at 10:19
  • @Nikhil, Did you check the Log.html? This problem also occur in simulator. – Vivek Shah Sep 06 '14 at 05:15
  • I have crossed check in debug mode that it reach expirationHandler but timer didn't stop. Still will look into it. – nikhil84 Sep 08 '14 at 05:30
  • ok in my case Log.html stops printing "ViewController.m ::methodFromTimer" after 3 minute. Tell me if you find any solution. – Vivek Shah Sep 09 '14 at 05:08
  • @VivekShah ok will try to debug for issue and also y do u want to print log in Log.html. What is exactly the aim of your app? – nikhil84 Sep 09 '14 at 05:57
  • I just want to make an API call at every 30 Sec even if my app is in background. Log.html is just for checking that Timer is firing or not. There is no use of it other then that. – Vivek Shah Sep 09 '14 at 06:56
  • But this API call every 30 sec will be a drawback on part of battery life and as iOS 8 is there apple has kept it's eye on to it. So my opinion would be not make such call. Rather go for push notification to check if your app exists or not. – nikhil84 Sep 09 '14 at 08:12
  • As in case a user have installed your app but never opened it after first initial run. Then no point in letting ur server know that app is still there. Rather fire a push notification letting user know that he/she hasn't opened that ap for quite long time. Also whenever user open ur app then fire an API letting ur server know app is opened and some sort of counter could be reset(counter to fire push notification). – nikhil84 Sep 09 '14 at 08:16
2

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

Prasad
  • 5,946
  • 3
  • 30
  • 36
2

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 { }
        }
    }
}
Fredrik Johansson
  • 3,477
  • 23
  • 37
1

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.

Community
  • 1
  • 1
Templar
  • 1,694
  • 1
  • 14
  • 32