161

I need to get the time elapsed between two events, for example, the appearance of a UIView and the user's first reaction.

How can I achieve it in Objective-C?

Pang
  • 9,564
  • 146
  • 81
  • 122
Ilya Suzdalnitski
  • 52,598
  • 51
  • 134
  • 168

7 Answers7

272
NSDate *start = [NSDate date];
// do stuff...
NSTimeInterval timeInterval = [start timeIntervalSinceNow];

timeInterval is the difference between start and now, in seconds, with sub-millisecond precision.

Can Berk Güder
  • 109,922
  • 25
  • 130
  • 137
  • 20
    @NicolasZozol you can use `fabs(...)` to get the absolute value of a float. Eg. `NSTimeInterval timeInterval = fabs([start timeIntervalSinceNow]);` – So Over It Jun 23 '12 at 05:01
  • 2
    seems the value of timeInterval is negative. – LiangWang Jan 13 '13 at 09:19
  • if you get a negative value - multiply with -1 and you're good – PinkFloydRocks Mar 08 '13 at 09:04
  • 10
    Relying on `[NSDate date]` could lead to difficult to track bugs, see [this answer](http://stackoverflow.com/a/17986909/35690) for more info. – Senseful Aug 01 '13 at 06:43
  • 1
    @PinkFloydRocks — What? How would a negative value ever legitimately occur? – Todd Lehman Mar 30 '16 at 01:17
  • @PinkFloydRocks — Ah! Yes, silly me. I wasn't reading closely enough. It's always either negative or zero. I was mistakenly thinking you were implying it was sometimes negative and sometimes positive. Sorry about that. – Todd Lehman Mar 30 '16 at 13:37
  • `[[NSDate date] timeIntervalSinceDate:start]` is a positive value too. – illusionJJ Dec 12 '16 at 08:42
  • This approach has unreasonably high CPU/memory overhead AND is subject to time jumps caused by time synchronization, time zone changes, preferences change, etc. Use `CACurrentMediaTime()` or `mach_absolute_time()` instead. – wonder.mice Jun 23 '18 at 05:04
238

You should not rely on [NSDate date] for timing purposes since it can over- or under-report the elapsed time. There are even cases where your computer will seemingly time-travel since the elapsed time will be negative! (E.g. if the clock moved backwards during timing.)

According to Aria Haghighi in the "Advanced iOS Gesture Recognition" lecture of the Winter 2013 Stanford iOS course (34:00), you should use CACurrentMediaTime() if you need an accurate time interval.

Objective-C:

#import <QuartzCore/QuartzCore.h>
CFTimeInterval startTime = CACurrentMediaTime();
// perform some action
CFTimeInterval elapsedTime = CACurrentMediaTime() - startTime;

Swift:

let startTime = CACurrentMediaTime()
// perform some action
let elapsedTime = CACurrentMediaTime() - startTime

The reason is that [NSDate date] syncs on the server, so it could lead to "time-sync hiccups" which can lead to very difficult-to-track bugs. CACurrentMediaTime(), on the other hand, is a device time that doesn't change with these network syncs.

You will need to add the QuartzCore framework to your target's settings.

Note: There are other API's like clock_gettime_nsec_np that may work as well.

Senseful
  • 86,719
  • 67
  • 308
  • 465
  • 17
    Please upvote this. Although I think everyone can agree that NSDate should be your first option, this suggestion resolved an issue of mine. When calling `[NSDate date]` within a completion block within a dispatch block, I was getting the same "current" time that was being reported by `[NSDate date]` immediately before execution hit the point at which my blocks were created. `CACurrentMediaTime()` solved this issue. – n00neimp0rtant Jan 23 '14 at 21:05
  • How would we use the elapsedTime for display like mm:ss? – CyberMew Sep 08 '14 at 08:35
  • @CyberMew: That is a separate problem. See [How do I break down an NSTimeInterval into year, months, days, hours, minutes and seconds on iPhone?](http://stackoverflow.com/questions/1237778/how-do-i-break-down-an-nstimeinterval-into-year-months-days-hours-minutes-an) for some solutions. – Senseful Sep 22 '14 at 19:19
  • 15
    Be aware that `CACurrentMediaTime()` stops ticking when the device enters sleep. If you test with the device disconnected from a computer, lock it, then wait ~10 minutes you will find that `CACurrentMediaTime()` does not keep up with wall clock time. – russbishop Apr 05 '15 at 21:13
  • add and import #import – steveen zoleko Sep 29 '15 at 11:10
  • Also, creating `NSDate` objects seems like a useless overhead. – Iulian Onofrei Jun 12 '17 at 13:18
26

Use the timeIntervalSinceDate method

NSTimeInterval secondsElapsed = [secondDate timeIntervalSinceDate:firstDate];

NSTimeInterval is just a double, define in NSDate like this:

typedef double NSTimeInterval;
Marco Lazzeri
  • 1,808
  • 19
  • 15
15

For anybody coming here looking for a getTickCount() implementation for iOS, here is mine after putting various sources together.

Previously I had a bug in this code (I divided by 1000000 first) which was causing some quantisation of the output on my iPhone 6 (perhaps this was not an issue on iPhone 4/etc or I just never noticed it). Note that by not performing that division first, there is some risk of overflow if the numerator of the timebase is quite large. If anybody is curious, there is a link with much more information here: https://stackoverflow.com/a/23378064/588476

In light of that information, maybe it is safer to use Apple's function CACurrentMediaTime!

I also benchmarked the mach_timebase_info call and it takes approximately 19ns on my iPhone 6, so I removed the (not threadsafe) code which was caching the output of that call.

#include <mach/mach.h>
#include <mach/mach_time.h>

uint64_t getTickCount(void)
{
    mach_timebase_info_data_t sTimebaseInfo;
    uint64_t machTime = mach_absolute_time();

    // Convert to milliseconds
    mach_timebase_info(&sTimebaseInfo);
    machTime *= sTimebaseInfo.numer;
    machTime /= sTimebaseInfo.denom;
    machTime /= 1000000; // convert from nanoseconds to milliseconds

    return machTime;
}

Do be aware of the potential risk of overflow depending on the output of the timebase call. I suspect (but do not know) that it might be a constant for each model of iPhone. on my iPhone 6 it was 125/3.

The solution using CACurrentMediaTime() is quite trivial:

uint64_t getTickCount(void)
{
    double ret = CACurrentMediaTime();
    return ret * 1000;
}
Wayne Uroda
  • 5,025
  • 4
  • 30
  • 35
  • Anyone know why there is a redundant (void) cast on the mach_timebase_info call? – Simon Tillson Dec 10 '13 at 10:57
  • @SimonTillson that's a good question - either it was required to silence a warning, or I copied it from somewhere else and just didn't notice to delete it. I can't remember. – Wayne Uroda Dec 12 '13 at 05:21
  • 2
    This is not thread safe. `mach_timebase_info()` will reset passed-in `mach_timebase_info_data_t` to `{0,0}` before setting it to actual values, which can cause division by zero if code is called from multiple threads. Use `dispatch_once()` instead (or `CACurrentMediaTime` which is just mach time converted to seconds). – wonder.mice Jun 23 '18 at 05:00
  • Thanks @wonder.mice, I have updated my answer (found an issue regarding quantisation as well, I blame 6-year-younger me...) – Wayne Uroda Nov 07 '18 at 12:54
8

For percise time measurements (like GetTickCount), also take a look at mach_absolute_time and this Apple Q&A: http://developer.apple.com/qa/qa2004/qa1398.html.

Jason Coco
  • 77,985
  • 20
  • 184
  • 180
4

use the timeIntervalSince1970 function of the NSDate class like below:

double start = [startDate timeIntervalSince1970];
double end = [endDate timeIntervalSince1970];
double difference = end - start;

basically, this is what i use to compare the difference in seconds between 2 different dates. also check this link here

Raj
  • 6,810
  • 6
  • 48
  • 56
1

The other answers are correct (with a caveat*). I add this answer simply to show an example usage:

- (void)getYourAffairsInOrder
{
    NSDate* methodStart = [NSDate date];  // Capture start time.

    // … Do some work …

    NSLog(@"DEBUG Method %s ran. Elapsed: %f seconds.", __func__, -([methodStart timeIntervalSinceNow]));  // Calculate and report elapsed time.
}

On the debugger console, you see something like this:

DEBUG Method '-[XMAppDelegate getYourAffairsInOrder]' ran. Elapsed: 0.033827 seconds.

*Caveat: As others mentioned, use NSDate to calculate elapsed time only for casual purposes. One such purpose might be common testing, crude profiling, where you just want a rough idea of how long a method is taking.

The risk is that the device's clock's current time setting could change at any moment because of network clock syncing. So NSDate time could jump forward or backward at any moment.

Pang
  • 9,564
  • 146
  • 81
  • 122
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154