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.
Related
I need to call a method in every 15 seconds irrespective of any fact, whether it is on any view controller in foreground, whether it is in background or it is killed, I need to call it at all times.
I know I can do the delay task using NSTimer
NSTimer* myTimer = [NSTimer scheduledTimerWithTimeInterval: 15.0 target: self
selector: #selector(callAfterFifteenSeconds:) userInfo: nil repeats: YES];
But, I wanted to know where to implement it so that it could fulfil my condition. I guess I can use it in App Delegate but I need a guidance for this to implement it correctly.
Calling it in App Delegate class is right place but it will not work for following cases.
It will not work if your app is killed from back ground.
It will not in background mode continuously. OS will stop that process after certain period of time.
-If the app is killed, you cannot do anything.
-When the app is in background, the OS may kill that process after certain time interval (I believe it is 15 seconds).
Though you can register for location changes, while the app is in background. In that case, your app will continue to receive location updates (such as for google maps).
-(void)callAfterFifteenSeconds {
//1.) do your work
//2.) If required, you can also choose to skip the next scheduling.
BOOL shouldSchedule = YES;
if (shouldSchedule) {
//3.)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(15 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//
[self callAfterFifteenSeconds];
});
}
}
[NSThread sleepForTimeInterval:4.0f];
dispatch_async(dispatch_get_main_queue(),
^{
//write you code if you want fire any method after 4 sec//
}
);
I am working on a alarm application. I faced a problem from last 2 weeks. My problem is: My application is running in background.First time I install the application and set the alarm & close the app. If the alarm time is more than 3 minutes from current time then its not ringing means after 3 min alarm is not ringing in background process. If application is on then alarm is working.
This is my code:
self->bgTask = 0;
NSAssert(self->bgTask == UIBackgroundTaskInvalid, nil);
bgTask = [application beginBackgroundTaskWithExpirationHandler: ^
{
NSLog(#"beginBackgroundTaskWithExpirat");
dispatch_async(dispatch_get_main_queue(), ^
{
NSLog(#"dispatch_async");
[application endBackgroundTask:self->bgTask];
self->bgTask = UIBackgroundTaskInvalid;
});
}];
dispatch_async(dispatch_get_main_queue(), ^
{
NSLog(#"dispatch_get_main_queue");
//Start BG Timer
[self Start_Update_Timer];
self->bgTask = UIBackgroundTaskInvalid;
});
// This is my timer for background running ..
-(void) Start_Update_Timer
{
//If timer is already running, stop the timer
if(self.callTimer)
{
[self.callTimer invalidate];
self.callTimer=nil;
}
//call the timer for every one sec
self.callTimer =[NSTimer scheduledTimerWithTimeInterval:60 target:self selector:#selector(update_Alarm_List) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:self.callTimer forMode:NSRunLoopCommonModes];
}
Because applications are not supposed to work like this. If you want your app to continuously play sound on the background, then it should be registered as an app that does it specially. Like a music player. If your app only delivers alarms, then you have to use a local notification to "trigger" the sound from your app. The notification can be played with a 30 secs sound and replay again at certain intervals. Your app wont play after 3 minutes because your "background" execution time has expired. That is the time that apple lets your app run in the background to do whatever you have to do after the user has closed your application.
This is for the case in where you want to keep playing sound on the background. Note that if the playback stops your app will stop.
https://developer.apple.com/library/ios/qa/qa1668/_index.html#//apple_ref/doc/uid/DTS40010209
If you want to use notifications with sound (30 secs max) see here:
https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/IPhoneOSClientImp.html
I need to create a iOS app where the app has to continuously check for the updates from the server(may be every 30 secs). But only when the app is running on the foreground.
I know this will drain the battery, but this will run on a environment where there's no internet. So we can't use push notifications.
Only option I can think of is sending a request to the server every 30 secs or so and get the updates. What is the best way to do this? Using NSTimer and NSURLConnection or any other better approaches?
Also if I use a timer, when the app goes to the background will it pause and will it start running as it comes to the foreground again? Is there a chance that app get killed while its on background?
Thanks
Using NSTimer and NSURLConnection or any other better approaches?
My first thought was also to use NSTimer and NSURLConnection.
Also if I use a timer, when the app goes to the background will it pause and will it start running as it comes to the foreground again?
Yes, it will. It doesn't exactly pause, but based on my testing in the simulator, the effect is similar. Let's say the timer is set to go off at 00:00:00, 00:00:30, 00:00:60, ... and you background the app at 00:00:15 and resume it at 00:00:45. The timer that was supposed to fire at 00:00:30 fires immediately when you resume (at 00:00:45), and the next firing (at 00:00:60) and subsequent firings are back on schedule.
Is there a chance that app get killed while its on background?
Yes, there is. But if you start the timer whenever the app launches, this shouldn't be a problem, right?
Your best bet is to setup a separate object that manages these operations on a background thread. Then in your app delegate, when
- (void)applicationWillResignActive:(UIApplication *)application
is called, have this special object stop all of it's synchronizing and clean up anything it needs to.
Then when:
- (void)applicationDidBecomeActive:(UIApplication *)application
gets called as the app gets active again, signal your object to query / poll on its background thread again.
Your custom object could have an interface like this
#interface PollingObject : NSObject
{
NSTimer* _timer;
NSUinteger _interval;
BOOL _cancel;
BOOL _isPolling;
dispatch_queue_t _pollQueue;
}
- (void)startPolling;
- (void)stopPolling;
#end
The implementation can be something like this:
#implementation PollingObject : NSObject
- (id)init
{
if (self = [super init])
{
_interval = 1; // 1 second interval
_cancel = NO; // default to NO
_isPolling = NO; // default to NO
// init your background queue
_pollQueue = dispatch_queue_create("com.yourconame.yourappname.pollQueue", NULL);
}
return self;
}
- (void)heartbeat
{
if (_cancel)
{
// stop the timer
[_timer invalidate];
_isPolling = NO;
return;
}
// Runs the polling method ONCE on a background queue
dispatch_async(_pollQueue, ^{
[self pollingMethod];
});
}
- (void)pollingMethod
{
// Do actual network polling work here...but only run it once. (don't loop)
}
- (void)startPolling
{
_cancel = NO;
if (_isPolling)
{
NSLog(#"Already polling");
return;
}
// schedule the method heartbeat to run every second
_timer = [NSTimer scheduledTimerWithTimeInterval:_interval target:self selector:#selector(heartbeat) userInfo:nil repeats:YES];
}
- (void)stopPolling
{
// we set the flag here and the next second the heartbeat will stop the timer
_cancel = YES;
}
#end
Look at Rocket real-time networking which looks easy to setup through AFNetworking 2.0.
https://github.com/AFNetworking/AFNetworking/wiki/AFNetworking-2.0-Migration-Guide
See the last part of this wiki. I have not used it but it would be something I would try if I had your requirements.
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.
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