Làm cách nào để có được thời gian chính xác, ví dụ tính bằng mili giây trong Objective-C?


99

Có cách nào dễ dàng để có được thời gian rất chính xác không?

Tôi cần tính toán một số độ trễ giữa các lần gọi phương thức. Cụ thể hơn, tôi muốn tính toán tốc độ cuộn trong UIScrollView.


đây là một câu hỏi liên quan rất nhiều mà cũng có thể giúp hiểu câu trả lời ở đây .. xin vui lòng xem!
abbood


Câu trả lời:


127

NSDatevà các timeIntervalSince*phương thức sẽ trả về a NSTimeIntervallà một nhân đôi với độ chính xác dưới mili giây. NSTimeIntervaltính bằng giây, nhưng nó sử dụng số kép để cung cấp cho bạn độ chính xác cao hơn.

Để tính toán độ chính xác thời gian mili giây, bạn có thể thực hiện:

// Get a current time for where you want to start measuring from
NSDate *date = [NSDate date];

// do work...

// Find elapsed time and convert to milliseconds
// Use (-) modifier to conversion since receiver is earlier than now
double timePassed_ms = [date timeIntervalSinceNow] * -1000.0;

Tài liệu về thời gianIntervalSinceNow .

Có nhiều cách khác để tính toán khoảng thời gian này bằng cách sử dụng NSDate, và tôi khuyên bạn nên xem tài liệu lớp học NSDateđược tìm thấy trong Tài liệu tham khảo lớp NSDate .


3
Trên thực tế, điều này là đủ chính xác cho trường hợp sử dụng chung.
logancautrell

4
Sử dụng NSDates để tính thời gian đã trôi qua có an toàn không? Có vẻ như với tôi rằng thời gian hệ thống có thể tiến lên hoặc lùi lại khi đồng bộ hóa với các nguồn thời gian bên ngoài, vì vậy bạn sẽ không thể tin tưởng vào kết quả. Bạn thực sự muốn một chiếc đồng hồ tăng đơn âm, phải không?
Kristopher Johnson

1
Tôi chỉ so sánh NSDatemach_absolute_time()ở mức khoảng 30ms. Đối với tôi, 27 so với 29, 36 so với 39, 43 so với 45. NSDatedễ sử dụng hơn và các kết quả cũng tương tự, đủ để không cần quan tâm.
nevan king, 31-07-13

7
Không an toàn khi sử dụng NSDate để so sánh thời gian đã trôi qua, vì đồng hồ hệ thống có thể thay đổi bất kỳ lúc nào (do chuyển đổi NTP, DST, giây nhuận và nhiều lý do khác). Sử dụng mach_absolute_time để thay thế.
nevyn

@nevyn bạn vừa lưu bài kiểm tra tốc độ internet của tôi, ty! Chết tiệt tôi vui vì tôi đọc các ý kiến trước khi copy dán mã heh
Albert Renshaw

41

mach_absolute_time() có thể được sử dụng để có được các phép đo chính xác.

Xem http://developer.apple.com/qa/qa2004/qa1398.html

Cũng có sẵn CACurrentMediaTime(), về cơ bản là giống nhau nhưng với giao diện dễ sử dụng hơn.

(Lưu ý: Câu trả lời này được viết vào năm 2009. Xem câu trả lời của Pavel Alexeev về các clock_gettime()giao diện POSIX đơn giản hơn có sẵn trong các phiên bản macOS và iOS mới hơn.)


1
Trong mã đó là một chuyển đổi kỳ lạ - dòng cuối cùng của ví dụ đầu tiên là "return * (uint64_t *) & elapsedNano;" tại sao không chỉ là "return (uint64_t) elapsedNano"?
Tyler

8
Core Animation (QuartzCore.framework) cũng cung cấp một phương thức tiện lợi CACurrentMediaTime(), chuyển đổi mach_absolute_time()trực tiếp thành a double.
otto

1
@Tyler, elapsedNanothuộc loại Nanoseconds, không phải là kiểu số nguyên đơn giản. Đó là một bí danh của UnsignedWide, là một cấu trúc có hai trường số nguyên 32 bit. Bạn có thể sử dụng UnsignedWideToUInt64()thay vì diễn viên nếu muốn.
Ken Thomases,

CoreServices chỉ là một thư viện Mac. Có tương đương trong iOS không?
mm24,

1
@ mm24 Trên iOS, sử dụng CACurrentMediaTime (), nằm trong Core Animation.
Kristopher Johnson

28

Xin đừng sử dụng NSDate, CFAbsoluteTimeGetCurrenthoặc gettimeofdayđể đo thời gian trôi qua. Tất cả những điều này phụ thuộc vào đồng hồ hệ thống, có thể thay đổi bất kỳ lúc nào do nhiều lý do khác nhau, chẳng hạn như đồng bộ hóa thời gian mạng (NTP) cập nhật đồng hồ (thường xảy ra để điều chỉnh độ trôi), điều chỉnh DST, giây nhuận, v.v.

Điều này có nghĩa là nếu bạn đang đo tốc độ tải xuống hoặc tải lên của mình, bạn sẽ nhận được những con số đột ngột hoặc giảm xuống không tương quan với những gì thực sự đã xảy ra; kiểm tra hiệu suất của bạn sẽ có những ngoại lệ không chính xác kỳ lạ; và bộ hẹn giờ thủ công của bạn sẽ kích hoạt sau khoảng thời gian không chính xác. Thời gian thậm chí có thể quay ngược trở lại, và bạn kết thúc với các delta âm, và bạn có thể kết thúc với đệ quy vô hạn hoặc mã chết (vâng, tôi đã làm cả hai điều này).

Sử dụng mach_absolute_time. Nó đo số giây thực kể từ khi hạt nhân được khởi động. Nó đang tăng một cách đơn điệu (sẽ không bao giờ đi ngược lại) và không bị ảnh hưởng bởi cài đặt ngày và giờ. Vì rất khó để làm việc, đây là một trình bao bọc đơn giản cung cấp cho bạn NSTimeInterval:

// LBClock.h
@interface LBClock : NSObject
+ (instancetype)sharedClock;
// since device boot or something. Monotonically increasing, unaffected by date and time settings
- (NSTimeInterval)absoluteTime;

- (NSTimeInterval)machAbsoluteToTimeInterval:(uint64_t)machAbsolute;
@end

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

@implementation LBClock
{
    mach_timebase_info_data_t _clock_timebase;
}

+ (instancetype)sharedClock
{
    static LBClock *g;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        g = [LBClock new];
    });
    return g;
}

- (id)init
{
    if(!(self = [super init]))
        return nil;
    mach_timebase_info(&_clock_timebase);
    return self;
}

- (NSTimeInterval)machAbsoluteToTimeInterval:(uint64_t)machAbsolute
{
    uint64_t nanos = (machAbsolute * _clock_timebase.numer) / _clock_timebase.denom;

    return nanos/1.0e9;
}

- (NSTimeInterval)absoluteTime
{
    uint64_t machtime = mach_absolute_time();
    return [self machAbsoluteToTimeInterval:machtime];
}
@end

2
Cảm ơn. Còn CACurrentMediaTime()từ QuartzCore thì sao?
Cœur

1
Lưu ý: người ta cũng có thể sử dụng @import Darwin;thay vì#include <mach/mach_time.h>
Cœur

@ Cœc CACurrentMediaTime của chúng tôi thực sự phải tương đương với mã của tôi. Tốt tìm thấy! (Tiêu đề cho biết "Đây là kết quả của việc gọi mach_absolute_time () và chuyển đổi đơn vị thành giây")
nevyn

"có thể thay đổi bất kỳ lúc nào do nhiều lý do khác nhau, chẳng hạn như đồng bộ hóa thời gian mạng (NTP) cập nhật đồng hồ (thường xảy ra để điều chỉnh độ trôi), điều chỉnh DST, giây nhuận, v.v." - Những lý do khác có thể là gì? Tôi đang quan sát thấy các bước nhảy lùi khoảng 50 ms trong khi không có kết nối Internet. Vì vậy, rõ ràng, không ai trong số những lý do này nên áp dụng ...
Falko

@Falko không chắc chắn, nhưng nếu tôi phải đoán, tôi cá rằng hệ điều hành sẽ tự bù trôi nếu nó nhận thấy các đồng hồ phần cứng khác nhau không đồng bộ?
nevyn

14

CFAbsoluteTimeGetCurrent()trả về thời gian tuyệt đối dưới dạng một doublegiá trị, nhưng tôi không biết độ chính xác của nó là gì - nó có thể chỉ cập nhật mỗi chục mili giây hoặc nó có thể cập nhật mỗi micro giây, tôi không biết.


2
Tuy nhiên, đó là giá trị dấu phẩy động có độ chính xác kép và cung cấp độ chính xác dưới mili giây. Một giá trị của 72,89674947369 giây không phải là hiếm ...
Jim Dovey

25
@JimDovey: Tôi phải nói rằng giá trị của 72.89674947369giây sẽ khá không phổ biến, nếu xét tất cả các giá trị khác nó có thể là. ;)
FreeAsInBeer

3
@Jim: Bạn có trích dẫn rằng nó cung cấp độ chính xác dưới mili giây (đây là một câu hỏi trung thực)? Tôi hy vọng mọi người ở đây hiểu được sự khác biệt giữa độ chính xác và độ chính xác .
Adam Rosenfield

5
CFAbsoluteTimeGetCurrent () gọi gettimeofday () trên OS X và GetSystemTimeAsFileTime () trên Windows. Đây là mã nguồn .
Jim Dovey

3
Ồ, và gettimeofday () được thực hiện bằng cách sử dụng bộ đếm thời gian Mach nano giây thông qua mach_absolute_time () ; đây là nguồn để triển khai trang chung của gettimeofday () trên Darwin / ARM: opensource.apple.com/source/Libc/Libc-763.12/arm/sys/…
Jim Dovey

10

Tôi sẽ KHÔNG sử dụng mach_absolute_time()vì nó truy vấn sự kết hợp của hạt nhân và bộ xử lý trong một thời gian tuyệt đối bằng cách sử dụng tick (có thể là thời gian hoạt động).

Những gì tôi sẽ sử dụng:

CFAbsoluteTimeGetCurrent();

Chức năng này được tối ưu hóa để khắc phục sự khác biệt trong phần mềm và phần cứng iOS và OSX.

Một cái gì đó Geekier

Thương số của sự khác biệt trong mach_absolute_time()AFAbsoluteTimeGetCurrent()luôn vào khoảng 24000011.154871

Đây là nhật ký ứng dụng của tôi:

Xin lưu ý rằng thời gian kết quả cuối cùng là sự khác biệt trong CFAbsoluteTimeGetCurrent()'s

 2012-03-19 21:46:35.609 Rest Counter[3776:707] First Time: 353900795.609040
 2012-03-19 21:46:36.360 Rest Counter[3776:707] Second Time: 353900796.360177
 2012-03-19 21:46:36.361 Rest Counter[3776:707] Final Result Time (difference): 0.751137
 2012-03-19 21:46:36.363 Rest Counter[3776:707] Mach absolute time: 18027372
 2012-03-19 21:46:36.365 Rest Counter[3776:707] Mach absolute time/final time: 24000113.153295
 2012-03-19 21:46:36.367 Rest Counter[3776:707] -----------------------------------------------------
 2012-03-19 21:46:43.074 Rest Counter[3776:707] First Time: 353900803.074637
 2012-03-19 21:46:43.170 Rest Counter[3776:707] Second Time: 353900803.170256
 2012-03-19 21:46:43.172 Rest Counter[3776:707] Final Result Time (difference): 0.095619
 2012-03-19 21:46:43.173 Rest Counter[3776:707] Mach absolute time: 2294833
 2012-03-19 21:46:43.175 Rest Counter[3776:707] Mach absolute time/final time: 23999753.727777
 2012-03-19 21:46:43.177 Rest Counter[3776:707] -----------------------------------------------------
 2012-03-19 21:46:46.499 Rest Counter[3776:707] First Time: 353900806.499199
 2012-03-19 21:46:55.017 Rest Counter[3776:707] Second Time: 353900815.016985
 2012-03-19 21:46:55.018 Rest Counter[3776:707] Final Result Time (difference): 8.517786
 2012-03-19 21:46:55.020 Rest Counter[3776:707] Mach absolute time: 204426836
 2012-03-19 21:46:55.022 Rest Counter[3776:707] Mach absolute time/final time: 23999996.639500
 2012-03-19 21:46:55.024 Rest Counter[3776:707] -----------------------------------------------------

Tôi đã kết thúc bằng cách sử dụng mach_absolute_time()với một mach_timebase_info_datavà sau đó đã làm (long double)((mach_absolute_time()*time_base.numer)/((1000*1000)*time_base.denom));. Để có được cơ sở thời gian tốt, bạn có thể làm(void)mach_timebase_info(&your_timebase);
Nate Symer

12
Theo tài liệu cho CFAbsoluteTimeGetCurrent (), "Thời gian hệ thống có thể giảm do đồng bộ hóa với tham chiếu thời gian bên ngoài hoặc do người dùng thay đổi đồng hồ rõ ràng." Tôi không hiểu tại sao mọi người lại muốn sử dụng một thứ như thế này để đo thời gian đã trôi qua, nếu nó có thể quay ngược lại.
Kristopher Johnson

6
#define CTTimeStart() NSDate * __date = [NSDate date]
#define CTTimeEnd(MSG) NSLog(MSG " %g",[__date timeIntervalSinceNow]*-1)

Sử dụng:

CTTimeStart();
...
CTTimeEnd(@"that was a long time:");

Đầu ra:

2013-08-23 15:34:39.558 App-Dev[21229:907] that was a long time: .0023

NSDatephụ thuộc vào đồng hồ hệ thống mà có thể bị thay đổi bất kỳ lúc nào, có khả năng dẫn đến sai hoặc thậm chí khoảng thời gian âm. Sử dụng mach_absolute_timethay thế để có được thời gian đã trôi qua chính xác.
Cœur

mach_absolute_timecó thể bị ảnh hưởng bởi thiết bị khởi động lại. Sử dụng thời gian máy chủ thay thế.
kelin

5

Ngoài ra, đây là cách tính toán 64-bit được NSNumberkhởi tạo với kỷ nguyên Unix trong mili giây, trong trường hợp đó là cách bạn muốn lưu trữ nó trong CoreData. Tôi cần điều này cho ứng dụng của mình tương tác với hệ thống lưu trữ ngày tháng theo cách này.

  + (NSNumber*) longUnixEpoch {
      return [NSNumber numberWithLongLong:[[NSDate date] timeIntervalSince1970] * 1000];
  }

3

Các chức năng dựa trên mach_absolute_timelà tốt cho các phép đo ngắn.
Nhưng đối với các phép đo dài, lưu ý quan trọng là chúng ngừng hoạt động khi thiết bị ở chế độ ngủ.

Có một chức năng để lấy thời gian kể từ khi khởi động. Nó không dừng lại trong khi ngủ. Ngoài ra, gettimeofdaykhông phải là đơn điệu, nhưng trong các thí nghiệm của tôi, tôi luôn thấy rằng thời gian khởi động thay đổi khi thời gian hệ thống thay đổi, vì vậy tôi nghĩ nó sẽ hoạt động tốt.

func timeSinceBoot() -> TimeInterval
{
    var bootTime = timeval()
    var currentTime = timeval()
    var timeZone = timezone()

    let mib = UnsafeMutablePointer<Int32>.allocate(capacity: 2)
    mib[0] = CTL_KERN
    mib[1] = KERN_BOOTTIME
    var size = MemoryLayout.size(ofValue: bootTime)

    var timeSinceBoot = 0.0

    gettimeofday(&currentTime, &timeZone)

    if sysctl(mib, 2, &bootTime, &size, nil, 0) != -1 && bootTime.tv_sec != 0 {
        timeSinceBoot = Double(currentTime.tv_sec - bootTime.tv_sec)
        timeSinceBoot += Double(currentTime.tv_usec - bootTime.tv_usec) / 1000000.0
    }
    return timeSinceBoot
}

Và kể từ iOS 10 và macOS 10.12, chúng tôi có thể sử dụng CLOCK_MONOTONIC:

if #available(OSX 10.12, *) {
    var uptime = timespec()
    if clock_gettime(CLOCK_MONOTONIC_RAW, &uptime) == 0 {
        return Double(uptime.tv_sec) + Double(uptime.tv_nsec) / 1000000000.0
    }
}

Tóm lại:

  • Date.timeIntervalSinceReferenceDate - thay đổi khi thời gian hệ thống thay đổi, không đơn điệu
  • CFAbsoluteTimeGetCurrent() - không đơn điệu, có thể đi lùi
  • CACurrentMediaTime() - ngừng đánh dấu khi thiết bị ở chế độ ngủ
  • timeSinceBoot() - không ngủ, nhưng có thể không đơn điệu
  • CLOCK_MONOTONIC - không ngủ, đơn âm, được hỗ trợ kể từ iOS 10

1

Tôi biết đây là một cái cũ nhưng ngay cả tôi cũng thấy mình lang thang qua nó một lần nữa, vì vậy tôi nghĩ tôi sẽ gửi tùy chọn của riêng mình ở đây.

Đặt cược tốt nhất là hãy xem bài đăng trên blog của tôi về điều này: Định giờ mọi thứ trong Objective-C: Đồng hồ bấm giờ

Về cơ bản, tôi đã viết một lớp dừng xem theo cách rất cơ bản nhưng được gói gọn lại để bạn chỉ cần thực hiện những việc sau:

[MMStopwatchARC start:@"My Timer"];
// your work here ...
[MMStopwatchARC stop:@"My Timer"];

Và bạn kết thúc với:

MyApp[4090:15203]  -> Stopwatch: [My Timer] runtime: [0.029]

trong nhật ký ...

Một lần nữa, hãy xem bài đăng của tôi để biết thêm một chút hoặc tải xuống tại đây: MMStopwatch.zip


Việc triển khai của bạn không được khuyến khích. NSDatephụ thuộc vào đồng hồ hệ thống mà có thể bị thay đổi bất kỳ lúc nào, có khả năng dẫn đến sai hoặc thậm chí khoảng thời gian âm. Sử dụng mach_absolute_timethay thế để có được thời gian đã trôi qua chính xác.
Cœur

1

Bạn có thể tính thời gian hiện tại tính bằng mili giây kể từ ngày 1 tháng 1 năm 1970 bằng cách sử dụng NSDate:

- (double)currentTimeInMilliseconds {
    NSDate *date = [NSDate date];
    return [date timeIntervalSince1970]*1000;
}

Điều này sẽ cho bạn thời gian trong mili giây nhưng nó vẫn còn ở giây chính xác
dev

-1

Đối với những người chúng tôi cần phiên bản Swift của câu trả lời của @Jeff Thompson:

// Get a current time for where you want to start measuring from
var date = NSDate()

// do work...

// Find elapsed time and convert to milliseconds
// Use (-) modifier to conversion since receiver is earlier than now
var timePassed_ms: Double = date.timeIntervalSinceNow * -1000.0

Tôi hy vọng điều này sẽ giúp bạn.


2
NSDatephụ thuộc vào đồng hồ hệ thống mà có thể bị thay đổi bất kỳ lúc nào, có khả năng dẫn đến sai hoặc thậm chí khoảng thời gian âm. Sử dụng mach_absolute_timethay thế để có được thời gian đã trôi qua chính xác.
Cœur
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.