1

I would like iOS to call my object's method at an exact time in the future. The time to call the method could be up to 4 hours from the current time.

I know I could use NSTimer, but I'm concerned it won't be precise enough for my application (i.e., within 30-50 ms): "A timer is not a real-time mechanism; it fires only when one of the run loop modes to which the timer has been added is running and able to check if the timer’s firing time has passed."

I've also seen mentions of using CADisplayLink for precision, but more in the context of interval timing.

Another alternative may be threading to avoid delays caused by the system run loop.

Which is considered "best" choice for what I want to do, or is there something else I should look at? Or is NSTimer precise enough if very little else is really going on in my app? Thanks!

Community
  • 1
  • 1
ScottyB
  • 2,167
  • 1
  • 30
  • 46

4 Answers4

3

Well one better alternate to NSTimer is GCD you can use dispatch_after to call a method after some time, its even better.

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // TO DO METHOD CALL.
});
Harish Suthar
  • 711
  • 1
  • 4
  • 14
2

NSTimer is definitely not the answer. If you want any kind of accuracy at all, I highly recommend you avoid NSTimer completely.

NSDate and will return an NSTimeInterval which is a double with sub ms accuracy. NSTimeInterval is in seconds, but it uses the double to give you greater precision.

Next on the chopping block is CoreAnimation with their function CFAbsoluteTimeGetCurrent. Many people think this is more precise than the previous options. It's not more accurate, or more precise. Also confusing is that this time function returns a value for system uptime. Since you're attempting to schedule something accurately, it would be pretty useless to you.

If you truly require greater precision, the only answer is mach_absolute_time. Take your time in understanding this class. This will return the time to you in processor dependent ticks. While it will require a bit of massaging to get back to wall time, since it's literally tracking CPU cycles, you CANNOT be more accurate than that.

Check out Apple's Tech Note on Precise Timing and Measurements.

EDIT: It seems like you want to run some code at a specified point in the future, the first and simplest solution that comes to my mind are UILocalNotifications. They handle timezones and tracking effortlessly, and since the fireDate is an NSDate you should see the same precision as another NSDate and vastly more accuracy than NSTimer.

Sam
  • 2,579
  • 17
  • 27
  • Thanks. I'm actually trying to run code at a specific time according to an external SNTP server. To do this, I'm calculating an "adjustment" to be applied to the future time. Will UILocalNotification call my method within 50ms of this time or (alternatively) should I schedule it, say, 5 seconds ahead of time, then use Grand Central Dispatch for more precision? (The code will need draw on the screen, if that makes any difference.) – ScottyB Apr 07 '14 at 16:30
  • Just getting back to this... UILocalNotification is not accurate. see http://stackoverflow.com/questions/5064460/uilocalnotification-is-not-firing-at-exact-time. I'm going to see how GCD goes. – ScottyB Apr 19 '14 at 02:13
  • 2
    I'm changing this to the correct answer. dispatch_after tended to fire significantly (.5+ seconds) late in my tests. So I ended up creating a new thread, then within it calling mach_wait_until() with a calculated delay before calling my object's method. Very precise. – ScottyB Apr 29 '14 at 17:28
  • 1
    @ScottyB, can you explain how you did that? Yours seems to be the only SO question asking about exact time, but in this comment you talk about a calculated delay--which sounds like all the other questions about relative time. If you need to fire something at exactly 5pm, how are you getting the "until" to feed into mach_wait_until()? – Le Mot Juiced Apr 16 '15 at 18:20
  • @LeMotJuiced If you post another question, I'm happy to answer it! – Sam Apr 16 '15 at 18:54
  • I'm happy to do that, it's just frustrating that this seems to be the exact question I have, and it seems like ScottyB got exactly what he wanted, I just can't understand it. I don't mind throwing you an easy green checkmark though. :) – Le Mot Juiced Apr 16 '15 at 19:06
  • Hi. In my situation, I had a visual countdown using an NSTimer. Then at .5 seconds until the target time within the NSTimer, I call mach_wait_until() with the precise interval (target time - current time) left before the method needs to be called. In my experience, however, NSTimer was actually reliable, but then my app isn't very CPU intensive. – ScottyB Apr 16 '15 at 19:24
  • @Sam: teed it up for you. :) Scotty: so you used the rough mechanism of the NSTimer (which you say in your original post isn't accurate enough) to get close to the target time, then you use mach_wait to get to the precise time? That's very helpful, but it still doesn't seem to answer the original question: how are you getting the definition of the target time? – Le Mot Juiced Apr 16 '15 at 19:29
  • I used the example shown here: https://developer.apple.com/library/ios/technotes/tn2169/_index.html, specifically listing 2, except time_to_wait = nanos_to_abs([myTargetTime timeIntervalSinceNow] * NANOS_PER_SEC); – ScottyB Apr 17 '15 at 00:13
  • By the way, mach_absolute_time may not quite do what you expect, because when the device sleeps the timer stops. If you need a timer that's accurate over a longer period of time, it might be a better idea to get KERN_BOOTTIME from sysctl(). CACurrentMediaTime()'s implementation uses mach_absolute_time so suffers the same issues. – flagoworld Jul 28 '16 at 20:54
1
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_async(queue, ^{
            double delayInSeconds = 0.2; 
            dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); 
            dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ [self doTimer];
        });
});

- (void) doTimer {
  // your code 
}
leopic
  • 2,958
  • 2
  • 27
  • 42
Buunk
  • 19
  • 3
0

I had the same problem with my last project and this is how I solved it. First, you need to save the time from when you start counting time. In my case, I chose to use NSDefault:

[[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:@"ReminderStart"]

Then when you need to decide whether to call your method, you get the reminder start time from NSDefault, and use it to compare with the current time:

NSCalendar *gregorianCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
    NSDateComponents *components = [gregorianCalendar components:NSMinuteCalendarUnit
                                                        fromDate:REMINDER_START_DATE
                                                          toDate:[NSDate date]
                                                         options:0];
if (components.minute > 30){
   //call your method here if the time difference is larger than 30 minutes
}

In my code I checked time difference in minutes, but you can totally adjust it to seconds, hours, days or weeks if you want. Hope that this helps.

huong
  • 4,534
  • 6
  • 33
  • 54
  • I'm not sure you understand what I was asking. I'm looking for a way to get a precise alarm to within 30-50 ms. – ScottyB Apr 07 '14 at 13:19
  • I guess what you need is to call that method even when your app is not active? If so please update your question, that's somehow misleading to be honest. – huong Apr 08 '14 at 02:28
  • No, if the app is not active/in the background, the method does not need to be called. The user will miss out! – ScottyB Apr 08 '14 at 04:54
  • If so I'm not sure you understand what I posted here. The idea is pretty simple: Once you have the start time for the alarm, all you need is to check the time difference to decide if it's time to call your method. You can put that check in the `viewWillAppear` of any view controller that needs it / or any other place that suits your need. The options in your question are all more complicated than necessary. If this is still not what you want, please explain further so I could help. – huong Apr 08 '14 at 06:40
  • Since the finest granularity for NSDateComponents is seconds (NSSecondCalendarUnit), will it be able to call a method with 50 ms of the required time? – ScottyB Apr 08 '14 at 20:26
  • I don't think there's anything wrong with converting from seconds to milliseconds. All you need is to divide it by 1000. – huong Apr 10 '14 at 02:45