NSTimer not reliable when transitioning to background - ios

I'm using an NSTimer to play an audio file every x number of seconds. The audio files are in sync with a constantly running process, so the interval of the NSTimer is the shortest it can be (to my knowledge):
self.updateTimer = [NSTimer scheduledTimerWithTimeInterval:0.02 target:self selector:#selector(updateTimerFired:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:self.updateTimer forMode:NSRunLoopCommonModes];
My problem occurs during the transition to the background state e.g. when the user clicks the home button. It seems that a latency occurs with the timer, and the "updateTimerFired:" method isn't called as reliably during the transition. This results in the audio files playing out of sync, although they eventually sync back up after the transition is complete. Note that my app incorporates the Audio background mode.
Is there a more reliable tool other than NSTimer to ensure that the latency does not occur when transitioning to the background?

Ended up replacing the NSTimer with dispatch_source_create:
self.timerQueue = dispatch_queue_create("updateTimerQueue", nil);
self.updateTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.timerQueue);
dispatch_source_set_timer(self.updateTimer, DISPATCH_TIME_NOW, (1 * NSEC_PER_SEC) / 50, (1 * NSEC_PER_SEC) / 20);
dispatch_source_set_event_handler(self.updateTimer, ^{
[self updateTimerFired];
});
dispatch_resume(self.updateTimer);
This provides a dedicated queue for the timer to perform on, and is not interrupted by a transition to the background.

Related

How vibrate upto 30 seconds iOS device in background while receiving push notification

I have implemented APNS for push notification. I am using custom sound for alert notification that will play 30 seconds siren audio file. that is working. while playing that sound I get only one time of vibration with the duration of 2 secs. I need the vibration upto 30 seconds . how can I achieve that.
this is the payload I am using.
{"aps":{"alert":"alert message","badge":1,"sound":"SIREN.caf"}}
I have tried implementing code for vibrating upto 30 seconds. it will be call on
didReceiveRemoteNotification
-(void)vibrateDevice{
AudioServicesPlaySystemSoundWithCompletion(kSystemSoundID_Vibrate, ^{
AudioServicesDisposeSystemSoundID(kSystemSoundID_Vibrate);
});
vibrateCount++;
if (vibrateCount<30) {
// [self performSelector:#selector(vibrateDevice) withObject:nil afterDelay:1.0];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { NSTimer* t = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(vibrateDevice) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:t forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
}
}
the problem is I can able to vibrate only in app foreground. when app
is killed or in background vibration code part is not working.

Periodic sending small data to server in background mode

I am implementing a kind of tracking app and i need to spot my location once and send it once per 10 - 20 seconds (period value is important and can't be exceeded).
To lower battery consumption i stop location updates. This works good in foreground, but how can i do it when app moved in background?
I looked info about background fetch, but it hasn't got precise time for periodic sending data
How can i perform this task?
You can start and Stop periodic location update while app is in background.
To achieve this add class from given link for Location Update.
After that import LocationTracker.h in your AppDelegate.
Add Below code in your didFinishLaunchingWithOptions.
let locationTracker : LocationTracker = LocationTracker();
locationTracker?.startLocationTracking();
In LocationTracker.m, you can set duration to restart update.Here i set 1 minute or 60 Seconds.
//Restart the locationMaanger after 1 minute
self.shareModel.timer = [NSTimer scheduledTimerWithTimeInterval:60 target:self
selector:#selector(restartLocationUpdates)
userInfo:nil
repeats:NO];
You can also set duration time for fetch Locations. Here I fetch location for 10 Seconds.
self.shareModel.delay10Seconds = [NSTimer scheduledTimerWithTimeInterval:10 target:self
selector:#selector(stopLocationDelayBy10Seconds)
userInfo:nil
repeats:NO];

Background task stopping when device locked?

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.

NSTimer while in background mode is working -- but shouldn't it NOT be working?

I've opted in to background location updates in an App I'm working on.
In my LocationManager class, I've got a method that looks like this:
- (void)beginUpdateTimer
{
[self.updateTimer invalidate];
self.updateTimer = [NSTimer timerWithTimeInterval:ForceUpdateDuration
target:self
selector:#selector(updateWithLastKnownLocation)
userInfo:nil
repeats:NO];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addTimer:_updateTimer forMode:NSRunLoopCommonModes];
}
The method -updateWithLastKnownLocation potentially calls beginUpdateTimer again.
In testing my app, I've discovered that the timer continues to fire upon the app moving in to the background, so long as I've enabled background location updates. Shouldn't this NOT be happening though? Can I rely on this?
Thanks!
Yes, you can rely on it, provided the timer was running at the time you went into the background.

Set a timer on a NSURLSession

I'm using NSURLSession with a rather inconsistent REST API. The API can take between 1-50 seconds to respond for reasons out of my control.
I want to let the user know that on long waits (say over 10 seconds), that the request is still processing. I do NOT want to timeout or terminate any requests, though I know this is possible with NSURLSession. I simply want to provide a "still working" popup (for which I am using TSMessages to create).
How would I go about timing this, particularly as the requests are running on a background thread?
You could use NSTimer.
Creates and returns a new NSTimer object and schedules it on the
current run loop in the default mode. After seconds seconds have
elapsed, the timer fires, sending the message aSelector to target.
// Schedule a timer for 10 seconds and then call the method alertUser:
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(alertUser:) userInfo:nil repeats:NO];
- (void)alertUser:(id)sender
{
// Alert the user
}
I would instantiate the NSTimer after you begin the NSURLSessonTask.
E.G.
self.dataTask = [self.session dataTaskWithRequest:theRequest];
[self.dataTask resume];
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(alertUser:) userInfo:nil repeats:NO];
Per #Rob's comment,
If you have scheduled a repeating NSTimer you should invalidate it in either
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
OR the completion block of NSURLSessionTask. If the NSTimer does not repeat there is no need to invalidate it as it will invalidate itself automatically. Note, once an NSTimer has been invalidated it cannot be reused.

Resources