NSTimer missing fire date when clock is manually set forward - objective-c

I am setting up a timer to run a specific function in the future like this:
pingTimer = [[NSTimer alloc] initWithFireDate:pingAtDate
interval:0
target:self
selector:#selector(ping:)
userInfo:nil
repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:pingTimer forMode:NSDefaultRunLoopMode];
The date is approximately 1 week in the future, so for testing purposes I (and others) have been setting the system clock forward 8 days to ensure that the specified event happens. The problem is that it doesn't happen.
When scheduling a few minutes in the future I have observed that the timer still does go off, but it appears to go off after a specific number of minutes. Say I schedule the timer for a date 5 minutes in the future, then I set the clock forward 1 hour, the timer does actually fire 5 minutes later, but since I set the clock forward 1 hour the time that it fires at no longer aligns with the time that it was scheduled to fire at.
This is not what I would expect to happen, as I am calling "initWithFireDate".
Although all of this seems wrong to me (and may be an interesting observation to others) the question is how do I ensure that the timer fires as soon as it notices that it's fire date is in the past (i.e. How do I ensure that my timer will fire when somebody moves the clock past the scheduled firing date).

There were some good comments left, but no complete answers. I am going to pull all the pertinent details together here.
An NSTimer is not a clock-time mechanism. When you set a "FireDate" you cannot be sure that the timer will actually fire at that date. You are actually telling the timer to run for specific amount of time before firing. That amount of time is the difference between when you add the timer to the run loop, and the date that you scheduled the timer to fire at.
If your system goes to sleep (or your application is suspended), your timer is no longer ticking down. It will resume ticking down when your system wakes up (or your application becomes active), but this means that your timer will now NOT execute at the original "FireDate". Rather it will execute at the "FireDate" + (amount of time your computer was asleep).
Similarly if a user changes the system time, this does not affect the timer in any way. If the timer was scheduled to fire at a date 8 hours in the future, it will continue ticking down 8 hours worth of time before it fires.
In the case where you want a timer to fire at a specific clock time in the distant future, you will need to make sure your application is notified of the following events:
Wake from sleep
System time change
When any of these events occur you will need to invalidate and adjust any existing timers.
/* If the clock time changed or we woke from sleep whe have to reset these long term timers */
- (void) resetTimers: (NSNotification*) notification
{
//Invalidate and Reset long term NSTimers
}
You can observe the following notifications to be notified when those events are going to happen.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(resetTimers:)
name:NSSystemClockDidChangeNotification
object:nil];
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self
selector:#selector(resetTimers:)
name:NSWorkspaceDidWakeNotification
object:nil];

Related

NSTimer slowing down and pausing when minimized [duplicate]

I'm working on a macOS app (let's call it the "display app") that displays a clock and other data, which is controlled by another app (the "control app") on the same machine via a TCP connection. I have noticed that when the display app is idle for some time (> 60 sec.) and then schedules an NSTimer (with a .2 second interval), it takes a very long time before the timer fires for the first time (in the range of 6-10 seconds, sometimes longer.) That happens mostly when the display app is not frontmost (because the control app is.) Once the timer fired for the first time, it works as expected (with some small, expected delays in the timer) for some time.
But when the timer is running for a long time (more than 5 minutes), there are similar extreme delays between firing (also 6-10 seconds.) It looks like manually scheduling the timer with
[[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
postpones the problem a bit (using [NSTimer scheduledTimer...] makes that problem appear sooner than when manually adding it to the runloop.)
This causes a lot of trouble because the clock is not updating during that time.
I assume this happens because macOS considers the display app "idle" or "inactive" in some way.
Is there a way to prevent, control, or circumvent this behaviour?
This is App Nap. The display app can do the following to avoid napping:
id activity = [[NSProcessInfo processInfo] beginActivityWithOptions:NSActivityUserInitiatedAllowingIdleSystemSleep reason:#"whatever"];
When it can allow napping again, you should do:
[[NSProcessInfo processInfo] endActivity:activity];

Prevent NSTimer firing delays in background app

I'm working on a macOS app (let's call it the "display app") that displays a clock and other data, which is controlled by another app (the "control app") on the same machine via a TCP connection. I have noticed that when the display app is idle for some time (> 60 sec.) and then schedules an NSTimer (with a .2 second interval), it takes a very long time before the timer fires for the first time (in the range of 6-10 seconds, sometimes longer.) That happens mostly when the display app is not frontmost (because the control app is.) Once the timer fired for the first time, it works as expected (with some small, expected delays in the timer) for some time.
But when the timer is running for a long time (more than 5 minutes), there are similar extreme delays between firing (also 6-10 seconds.) It looks like manually scheduling the timer with
[[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
postpones the problem a bit (using [NSTimer scheduledTimer...] makes that problem appear sooner than when manually adding it to the runloop.)
This causes a lot of trouble because the clock is not updating during that time.
I assume this happens because macOS considers the display app "idle" or "inactive" in some way.
Is there a way to prevent, control, or circumvent this behaviour?
This is App Nap. The display app can do the following to avoid napping:
id activity = [[NSProcessInfo processInfo] beginActivityWithOptions:NSActivityUserInitiatedAllowingIdleSystemSleep reason:#"whatever"];
When it can allow napping again, you should do:
[[NSProcessInfo processInfo] endActivity:activity];

iOS auto logout of application

I am wanting to create a timer or something of sorts to auto logout the user after x minutes of inactivity. I would like to do it the same way the Bank of America application does it. The way the BofA app does it is even when the application is put into the background it still keeps track of the time. When the time limit is reached a notification will popup stating you are being logged out.
How can this be done without the timer being suspended when the application goes into the background?
I think maybe the simplest thing you can do is register your AppDelegate with the NSNotificationCenter to listen for all events from all (or maybe specific) senders.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(resetTimer) name:nil object:nil];
You need to take care with passing nil to the name and object parameters as you will get a ton of notifications (even some not originating from the application, i.e., memory warnings). If you know of or have the list of specific event names and/or objects I would observe on those instead.
In your resetTimer method, you will simply invalidate the previous timer and create a new one that will call some logout method AND set an iVar to the current date/time (i.e., timerStart = [NSDate now];)
The above steps will take care of your app while it is in the foreground.
When the app is backgrounded, the timers will quit working. However, when the app returns to the foreground, you can calculate the delta between [NSDate now] and your timerStart iVar. If the delta is greater than some interval, you invoke your logout method. If not, you can just call resetTimer to start your timers again.
EDIT
If you want the backgrounded app to alert that the user is about to be logged out, you can use a UILocalNotification. You can schedule one to alert when the application enters the background. When the application enters the foreground, you can cancel that notification (and perform the steps I mention above).

NSTimer: Getting firing to NOT act retroactively

I'm currently using the snippet of code presented below to fire some methods every second. My app is running in the background. The problem is that if the computer wakes up after a sleep period the timer wants to retroactively fire all the methods it has missed. Similar issues come up if the user were to change the System Clock time.
Basically I want to implement the proper timer method that will have my methods called only every current second. If a second (or minute or hour or day) has passed and for whatever reason the methods weren't called I want my app to just continue from the current moment in time.
Also, can we keep this while using NSTimer?
Thanks!
-(void)start
{
NSTimer * timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(tasks:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode: NSDefaultRunLoopMode];
}
To handle the big time changes you can use the link UIApplicationSignificantTimeChangeNotification and unregister/reregister your timer.
To deal with the sleep issue, you can unregister and then reregister your timer whenever the machine goes to sleep and wakes up. See this technical note for information on how to do that. This solution won't work for changing the system time, though.

How to display time in seconds in Cocoa efficiently?

I want to display the time (including seconds) in an application in a text field, pretty simple. What I currently do is create a NSTimer that runs every 0.1 seconds that gets the system time and updates the text field. This seems like a terribly inefficient way to accomplish this. Is there a better way?
Are you displaying it to tenth-of-a-second (or finer) resolution?
If so, I see no problem. Usually, polling sucks because what you're checking might not have changed, but it's not like time is going to stop on you. So a tenth-of-a-second timer is fine.
If not, create a timer whose interval is the desired resolution, then get its fire date, round it down to the desired resolution (e.g., a whole second if you update every second), and set that as the new fire date. Then, your timer will only fire coincidentally with the times it's appropriate to update your clock view.
Note that while time always moves, it doesn't always move linearly: it may jump ahead or backward by an hour or half an hour, or any amount if the user (or ntpd) changes the system clock setting. On Mac OS X 10.6 and later, you can observe for the NSSystemClockDidChangeNotification, and re-adjust your timer's fire date when that happens.
How about you use NSTimer that runs every second and then you check the firing time and make sure it is exactly on the start of a second. You can use initWithFireDate:interval:target:selector:userInfo:repeats: or set the next invocation time appropriately using setFireDate: after the timer was created:
NSTimeInterval interval = [[timer fireDate] timeIntervalSinceReferenceDate];
NSTimeInterval nextFire = ceil(interval);
[timer setFireDate: [NSDate dateWithTimeIntervalSinceReferenceDate: nextFire];
Then you'll be sure that the timer fires as close to the needed time as possible and don't worry about the timer lagging behind because it resets itself after each firing using intended firing date and interval you provided, so it will always try to fire at exact second (but will fire whenever the run loop will be able to do that).
I'm not sure what else would be more efficient. As long as the callback for your timer is not doing anything too intensive, you're probably close to optimal.
How about using gettimeofday(2)? It returns much exact time information to use.