ฉันจะหาเวลาที่แน่นอนได้อย่างไรเช่นในหน่วยมิลลิวินาทีใน Objective-C


100

มีวิธีง่ายๆในการหาเวลาอย่างแม่นยำหรือไม่?

ฉันต้องการคำนวณความล่าช้าระหว่างการเรียกใช้เมธอด โดยเฉพาะอย่างยิ่งฉันต้องการคำนวณความเร็วในการเลื่อนใน UIScrollView


นี่คือคำถามที่เกี่ยวข้องซึ่งอาจช่วยให้เข้าใจคำตอบได้ที่นี่ .. โปรดดู!
abbood


คำตอบ:


128

NSDateและtimeIntervalSince*วิธีการจะส่งกลับNSTimeIntervalซึ่งเป็นสองเท่าที่มีความแม่นยำย่อยมิลลิวินาที NSTimeIntervalเป็นหน่วยวินาที แต่จะใช้การเพิ่มสองครั้งเพื่อให้คุณมีความแม่นยำ

ในการคำนวณความแม่นยำของเวลาในระดับมิลลิวินาทีคุณสามารถทำได้:

// 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;

เอกสารตรงเวลา

มีวิธีการอื่น ๆ อีกมากมายในการคำนวณช่วงเวลานี้โดยใช้เป็นNSDateฉันจะแนะนำให้ดูที่เอกสารประกอบการเรียนสำหรับและNSDateซึ่งพบได้ในNSDate ชั้นอ้างอิง


3
อันที่จริงนี้แม่นยำเพียงพอสำหรับกรณีการใช้งานทั่วไป
logancautrell

4
ปลอดภัยหรือไม่ที่จะใช้ NSDates เพื่อคำนวณเวลาที่ผ่านไป สำหรับฉันแล้วดูเหมือนว่าเวลาของระบบสามารถเดินหน้าหรือถอยหลังได้เมื่อซิงค์กับแหล่งเวลาภายนอกดังนั้นคุณจะไม่สามารถเชื่อถือผลลัพธ์ได้ คุณต้องการนาฬิกาที่เพิ่มความซ้ำซากจำเจใช่ไหม?
Kristopher Johnson

1
ฉันเพิ่งเปรียบเทียบNSDateและmach_absolute_time()อยู่ในระดับประมาณ 30 มิลลิวินาที 27 เทียบกับ 29, 36 เทียบกับ 39, 43 เทียบกับ 45 ใช้NSDateง่ายกว่าสำหรับฉันและผลลัพธ์ก็คล้ายกันมากพอที่จะไม่สนใจ
nevan king

7
ไม่ปลอดภัยที่จะใช้ NSDate เพื่อเปรียบเทียบเวลาที่ผ่านไปเนื่องจากนาฬิกาของระบบสามารถเปลี่ยนแปลงได้ตลอดเวลา (เนื่องจาก NTP การเปลี่ยน DST วินาทีอธิกสุรทินและเหตุผลอื่น ๆ อีกมากมาย) ใช้ mach_absolute_time แทน
nevyn

@nevyn คุณเพิ่งบันทึกการทดสอบความเร็วอินเทอร์เน็ตของฉัน ty! ประณามฉันดีใจที่ได้อ่านความคิดเห็นก่อนที่จะคัดลอกวางรหัส heh
Albert Renshaw

41

mach_absolute_time() สามารถใช้เพื่อรับการวัดที่แม่นยำ

โปรดดูhttp://developer.apple.com/qa/qa2004/qa1398.html

นอกจากนี้ยังมีCACurrentMediaTime()ซึ่งโดยพื้นฐานแล้วจะเหมือนกัน แต่มีอินเทอร์เฟซที่ใช้งานง่ายกว่า

(หมายเหตุ: คำตอบนี้เขียนขึ้นในปี 2009 ดูคำตอบของ Pavel Alexeev สำหรับclock_gettime()อินเทอร์เฟซPOSIX ที่ง่ายกว่าซึ่งมีอยู่ใน macOS และ iOS เวอร์ชันใหม่กว่า)


1
ในโค้ดนั้นเป็นการแปลงแปลก ๆ - บรรทัดสุดท้ายของตัวอย่างแรกคือ "return * (uint64_t *) & elapsedNano;" ทำไมไม่แค่ "return (uint64_t) elapsedNano" ล่ะ
Tyler

8
Core Animation (QuartzCore.framework) ยังมีวิธีอำนวยความสะดวกCACurrentMediaTime()ซึ่งจะแปลงmach_absolute_time()เป็นไฟล์double.
otto

1
@Tyler elapsedNanoเป็นประเภทNanosecondsซึ่งไม่ใช่ประเภทจำนวนเต็มธรรมดา เป็นนามแฝงUnsignedWideซึ่งเป็นโครงสร้างที่มีช่องจำนวนเต็ม 32 บิตสองช่อง คุณสามารถใช้UnsignedWideToUInt64()แทนนักแสดงได้หากต้องการ
Ken Thomases

CoreServices เป็นเพียงไลบรารีของ Mac มีเทียบเท่าใน iOS หรือไม่?
24

1
@ mm24 บน iOS ให้ใช้ CACurrentMediaTime () ซึ่งอยู่ใน Core Animation
Kristopher Johnson

29

กรุณาอย่าใช้NSDate, CFAbsoluteTimeGetCurrentหรือgettimeofdayการวัดเวลาที่ผ่านไป ทั้งหมดนี้ขึ้นอยู่กับนาฬิกาของระบบซึ่งสามารถเปลี่ยนแปลงได้ตลอดเวลาเนื่องจากสาเหตุหลายประการเช่นการซิงค์เวลาเครือข่าย (NTP) การอัปเดตนาฬิกา (มักเกิดขึ้นเพื่อปรับเวลาดริฟท์) การปรับ DST วินาทีอธิกสุรทินและอื่น ๆ

ซึ่งหมายความว่าหากคุณวัดความเร็วในการดาวน์โหลดหรืออัปโหลดคุณจะได้รับตัวเลขที่เพิ่มขึ้นอย่างรวดเร็วหรือลดลงอย่างกะทันหันซึ่งไม่สัมพันธ์กับสิ่งที่เกิดขึ้น การทดสอบประสิทธิภาพของคุณจะมีค่าผิดปกติแปลก ๆ และตัวจับเวลาด้วยตนเองของคุณจะทำงานหลังจากระยะเวลาที่ไม่ถูกต้อง เวลาอาจย้อนกลับไปและคุณจะจบลงด้วยเดลต้าเชิงลบและคุณสามารถจบลงด้วยการเรียกซ้ำไม่สิ้นสุดหรือรหัสตาย (ใช่ฉันได้ทำทั้งสองอย่างนี้แล้ว)

ใช้mach_absolute_time. จะวัดวินาทีจริงตั้งแต่เริ่มต้นเคอร์เนล มันเพิ่มขึ้นอย่างจำเจ (จะไม่ถอยหลัง) และไม่ได้รับผลกระทบจากการตั้งค่าวันที่และเวลา เนื่องจากมันเป็นเรื่องยากที่จะทำงานด้วยนี่คือกระดาษห่อตัวง่ายๆที่ให้คุณ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
ขอบคุณ. แล้วCACurrentMediaTime()จาก QuartzCore ล่ะ?
Cœur

1
หมายเหตุ: สามารถใช้@import Darwin;แทน#include <mach/mach_time.h>
Cœur

@ Cœur CACurrentMediaTime ควรจะเทียบเท่ากับรหัสของฉันจริงๆ หาดี! (ส่วนหัวระบุว่า "นี่คือผลลัพธ์ของการเรียก mach_absolute_time () และแปลงหน่วยเป็นวินาที")
nevyn

"สามารถเปลี่ยนแปลงได้ตลอดเวลาเนื่องจากสาเหตุหลายประการเช่นการซิงค์เวลาเครือข่าย (NTP) การอัปเดตนาฬิกา (มักเกิดขึ้นเพื่อปรับเวลาดริฟท์) การปรับ DST วินาทีอธิกสุรทินและอื่น ๆ " - มีเหตุผลอะไรอีกบ้าง? ฉันสังเกตเห็นการกระโดดถอยหลังประมาณ 50 มิลลิวินาทีในขณะที่ไม่มีการเชื่อมต่ออินเทอร์เน็ต เห็นได้ชัดว่าเหตุผลเหล่านี้ไม่ควรใช้ ...
Falko

@Falko ไม่แน่ใจ แต่ถ้าฉันต้องเดาฉันจะเดิมพันว่าระบบปฏิบัติการจะชดเชยการลอยตัวด้วยตัวเองหากสังเกตเห็นว่านาฬิกาฮาร์ดแวร์อื่นไม่ตรงกัน?
nevyn

15

CFAbsoluteTimeGetCurrent()คืนค่าเวลาสัมบูรณ์เป็นdoubleค่า แต่ฉันไม่รู้ว่าความแม่นยำคืออะไร - อาจอัปเดตทุก ๆ สิบมิลลิวินาทีเท่านั้นหรืออาจอัปเดตทุก ๆ ไมโครวินาทีฉันไม่รู้


2
แม้ว่าจะเป็นค่าทศนิยมที่มีความแม่นยำสองเท่าและให้ความแม่นยำในระดับมิลลิวินาทีย่อย ค่า 72.89674947369 วินาทีไม่ใช่เรื่องแปลก ...
Jim Dovey

25
@JimDovey: ฉันจะต้องบอกว่าค่าของ72.89674947369วินาทีจะค่อนข้างผิดปกติเมื่อพิจารณาจากค่าอื่น ๆ ทั้งหมดที่อาจเป็นได้ ;)
FreeAsInBeer

3
@ จิม: คุณมีการอ้างอิงว่ามันให้ความแม่นยำในระดับมิลลิวินาทีย่อยหรือไม่ (นี่เป็นคำถามที่ซื่อสัตย์)? ฉันหวังทุกคนที่นี่มีความเข้าใจในความแตกต่างระหว่างความถูกต้องและความแม่นยำ
Adam Rosenfield

5
CFAbsoluteTimeGetCurrent () เรียก gettimeofday () บน OS X และ GetSystemTimeAsFileTime () บน Windows นี่คือรหัสที่มา
Jim Dovey

3
โอ้และ gettimeofday () ถูกนำไปใช้โดยใช้ตัวจับเวลานาโนวินาทีของ Mach ผ่านmach_absolute_time () ; นี่คือแหล่งที่มาของการใช้งาน gettimeofday () บนดาร์วิน / ARM: opensource.apple.com/source/Libc/Libc-763.12/arm/sys/…
Jim Dovey

10

ฉันจะไม่ใช้mach_absolute_time()เพราะมันค้นหาการรวมกันของเคอร์เนลและโปรเซสเซอร์เป็นเวลาที่แน่นอนโดยใช้เห็บ (อาจเป็นเวลาทำงาน)

สิ่งที่ฉันจะใช้:

CFAbsoluteTimeGetCurrent();

ฟังก์ชันนี้ได้รับการปรับให้เหมาะสมเพื่อแก้ไขความแตกต่างของซอฟต์แวร์และฮาร์ดแวร์ iOS และ OSX

บางสิ่ง Geekier

ผลหารของความแตกต่างในmach_absolute_time()และAFAbsoluteTimeGetCurrent()เป็นประมาณ 24000011.154871 เสมอ

นี่คือบันทึกแอปของฉัน:

โปรดทราบว่าเวลาผลลัพธ์สุดท้ายเป็นความแตกต่างCFAbsoluteTimeGetCurrent()ของ

 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] -----------------------------------------------------

ฉันสิ้นสุดที่ใช้mach_absolute_time()ด้วยและแล้วก็mach_timebase_info_data (long double)((mach_absolute_time()*time_base.numer)/((1000*1000)*time_base.denom));หากต้องการรับฐานเวลาของเครื่องจักรคุณสามารถทำได้(void)mach_timebase_info(&your_timebase);
Nate Symer

12
ตามเอกสารของ CFAbsoluteTimeGetCurrent () "เวลาของระบบอาจลดลงเนื่องจากการซิงโครไนซ์กับการอ้างอิงเวลาภายนอกหรือเนื่องจากการเปลี่ยนนาฬิกาของผู้ใช้อย่างชัดเจน" ฉันไม่เข้าใจว่าทำไมใคร ๆ ก็อยากใช้อะไรแบบนี้เพื่อวัดเวลาที่ผ่านไปถ้ามันสามารถย้อนกลับได้
Kristopher Johnson

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

การใช้งาน:

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

เอาท์พุต:

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

NSDateขึ้นอยู่กับนาฬิกาของระบบซึ่งสามารถเปลี่ยนแปลงได้ตลอดเวลาซึ่งอาจส่งผลให้ช่วงเวลาผิดพลาดหรือแม้แต่ติดลบ ใช้mach_absolute_timeแทนเพื่อดูเวลาที่ผ่านไปอย่างถูกต้อง
Cœur

mach_absolute_timeอาจได้รับผลกระทบจากการรีบูตอุปกรณ์ ใช้เวลาเซิร์ฟเวอร์แทน
kelin

5

นอกจากนี้วิธีการคำนวณ 64 บิตNSNumberเริ่มต้นด้วย Unix epoch ในหน่วยมิลลิวินาทีในกรณีที่คุณต้องการจัดเก็บใน CoreData ฉันต้องการสิ่งนี้สำหรับแอปของฉันที่โต้ตอบกับระบบที่จัดเก็บวันที่ด้วยวิธีนี้

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

3

ฟังก์ชั่นที่ใช้mach_absolute_timeเหมาะสำหรับการวัดระยะสั้น
แต่สำหรับการวัดระยะยาวข้อแม้ที่สำคัญก็คือพวกเขาจะหยุดติ๊กในขณะที่อุปกรณ์อยู่ในโหมดสลีป

มีฟังก์ชั่นรับเวลาตั้งแต่บูต มันไม่หยุดในขณะนอนหลับ นอกจากนี้gettimeofdayไม่ได้เป็นเสียงเดียว แต่ในการทดลองของฉันฉันมักจะเห็นว่าเวลาในการบูตจะเปลี่ยนไปเมื่อเวลาของระบบเปลี่ยนไปดังนั้นฉันคิดว่ามันน่าจะใช้ได้ดี

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
}

และตั้งแต่ iOS 10 และ macOS 10.12 เราสามารถใช้ 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
    }
}

สรุปได้:

  • Date.timeIntervalSinceReferenceDate - เปลี่ยนแปลงเมื่อเวลาของระบบเปลี่ยนไปไม่ใช่เสียงเดียว
  • CFAbsoluteTimeGetCurrent() - ไม่ใช่เสียงเดียวอาจย้อนกลับได้
  • CACurrentMediaTime() - หยุดติ๊กเมื่ออุปกรณ์อยู่ในโหมดสลีป
  • timeSinceBoot() - ไม่ได้นอนหลับ แต่อาจไม่ใช่เสียงเดียว
  • CLOCK_MONOTONIC - ไม่นอนไม่หลับเสียงเดียวรองรับตั้งแต่ iOS 10

1

ฉันรู้ว่านี่เป็นเรื่องเก่า แต่ฉันก็พบว่าตัวเองหลงทางอีกครั้งดังนั้นฉันคิดว่าฉันจะส่งตัวเลือกของตัวเองมาที่นี่

ทางออกที่ดีที่สุดคือตรวจสอบโพสต์บล็อกของฉันเกี่ยวกับสิ่งนี้: จับเวลาใน Objective-C: นาฬิกาจับเวลา

โดยพื้นฐานแล้วฉันเขียนคลาสที่หยุดดูด้วยวิธีพื้นฐาน แต่ถูกห่อหุ้มไว้เพื่อให้คุณต้องทำสิ่งต่อไปนี้เท่านั้น

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

และคุณจะจบลงด้วย:

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

ในบันทึก ...

ตรวจสอบโพสต์ของฉันอีกครั้งหรือดาวน์โหลดได้ที่นี่: MMStopwatch.zip


ไม่แนะนำให้ใช้งานของคุณ NSDateขึ้นอยู่กับนาฬิกาของระบบซึ่งสามารถเปลี่ยนแปลงได้ตลอดเวลาซึ่งอาจส่งผลให้ช่วงเวลาผิดพลาดหรือแม้แต่ติดลบ ใช้mach_absolute_timeแทนเพื่อดูเวลาที่ผ่านไปอย่างถูกต้อง
Cœur

1

คุณสามารถรับเวลาปัจจุบันเป็นมิลลิวินาทีตั้งแต่วันที่ 1 มกราคม 1970 โดยใช้ NSDate:

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

สิ่งนี้ให้เวลาคุณเป็นมิลลิวินาที แต่ยังคงความแม่นยำเป็นวินาที
dev

-1

สำหรับผู้ที่เราต้องการคำตอบของ @Jeff Thompson เวอร์ชัน Swift:

// 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

ฉันหวังว่านี่จะช่วยคุณได้


2
NSDateขึ้นอยู่กับนาฬิกาของระบบซึ่งสามารถเปลี่ยนแปลงได้ตลอดเวลาซึ่งอาจส่งผลให้ช่วงเวลาผิดพลาดหรือแม้แต่ติดลบ ใช้mach_absolute_timeแทนเพื่อดูเวลาที่ผ่านไปอย่างถูกต้อง
Cœur
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.