Como posso obter um tempo preciso, por exemplo em milissegundos em Objective-C?

99

Existe uma maneira fácil de obter uma hora com muita precisão?

Preciso calcular alguns atrasos entre chamadas de método. Mais especificamente, desejo calcular a velocidade de rolagem em um UIScrollView.

obrigado
fonte
aqui está uma questão muito relacionada que também pode ajudar a entender as respostas aqui .. por favor, dê uma olhada!
abbood,

Respostas:

127

NSDatee os timeIntervalSince*métodos retornarão a NSTimeIntervalque é um double com precisão de submilissegundos. NSTimeIntervalé em segundos, mas usa o dobro para dar a você maior precisão.

Para calcular a precisão do tempo em milissegundos, você pode fazer:

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

Documentação em timeIntervalSinceNow .

Existem muitas outras maneiras de calcular esse intervalo usando NSDate, e eu recomendaria olhar a documentação da classe NSDateencontrada em Referência de classe NSDate .

Jeff Thompson
fonte
3
Na verdade, isso é preciso o suficiente para o caso de uso geral.
logancautrell 02 de
4
É seguro usar NSDates para calcular o tempo decorrido? Parece-me que a hora do sistema pode avançar ou retroceder ao sincronizar com fontes de hora externas, por isso não seria capaz de confiar no resultado. Você realmente quer um relógio que aumenta monotonicamente, não é?
Kristopher Johnson
1
Acabei de comparar NSDatee mach_absolute_time()em cerca de 30ms de nível. 27 vs. 29, 36 vs. 39, 43 vs. 45. NSDatefoi mais fácil de usar para mim e os resultados foram semelhantes o suficiente para não me importar.
nevan king
7
Não é seguro usar NSDate para comparar o tempo decorrido, uma vez que o relógio do sistema pode mudar a qualquer momento (devido ao NTP, transições DST, segundos intercalados e muitos outros motivos). Use mach_absolute_time em vez disso.
nevyn
@nevyn, você acabou de salvar meu teste de velocidade de internet, ty! Porra, estou feliz por ter lido os comentários antes de copiar e colar o código heh
Albert Renshaw
41

mach_absolute_time() pode ser usado para obter medições precisas.

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

Também está disponível CACurrentMediaTime(), que é essencialmente a mesma coisa, mas com uma interface mais fácil de usar.

(Observação: essa resposta foi escrita em 2009. Consulte a resposta de Pavel Alexeev para as clock_gettime()interfaces POSIX mais simples disponíveis nas versões mais recentes do macOS e iOS.)

Kristopher Johnson
fonte
1
Nesse código há uma conversão estranha - a última linha do primeiro exemplo é "return * (uint64_t *) & elapsedNano;" por que não apenas "return (uint64_t) elapsedNano"?
Tyler
8
Core Animation (QuartzCore.framework) também fornece um método de conveniência,, CACurrentMediaTime()que converte mach_absolute_time()diretamente em um double.
otto
1
@Tyler, elapsedNanoé do tipo Nanoseconds, que não é um tipo inteiro simples. É um alias de UnsignedWide, que é uma estrutura com dois campos inteiros de 32 bits. Você pode usar em UnsignedWideToUInt64()vez do elenco, se preferir.
Ken Thomases
CoreServices é apenas uma biblioteca Mac. Existe um equivalente no iOS?
mm24
1
@ mm24 No iOS, use CACurrentMediaTime (), que está no Core Animation.
Kristopher Johnson
28

Por favor, não use NSDate, CFAbsoluteTimeGetCurrentou gettimeofdaypara medir o tempo decorrido. Tudo isso depende do relógio do sistema, que pode mudar a qualquer momento devido a muitos motivos diferentes, como sincronização de horário da rede (NTP), atualização do relógio (geralmente ocorre para ajustar para desvio), ajustes de DST, segundos intercalados e assim por diante.

Isso significa que, se você estiver medindo a velocidade de download ou upload, terá picos ou quedas repentinas em seus números que não se correlacionam com o que realmente aconteceu; seus testes de desempenho terão outliers incorretos estranhos; e seus temporizadores manuais serão acionados após durações incorretas. O tempo pode até retroceder e você acabar com deltas negativos, e você pode acabar com recursão infinita ou código morto (sim, eu fiz os dois).

Use mach_absolute_time. Ele mede os segundos reais desde que o kernel foi inicializado. É monotonicamente crescente (nunca retrocede) e não é afetado pelas configurações de data e hora. Como é difícil trabalhar com ele, aqui está um invólucro simples que fornece 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
nevyn
fonte
2
Obrigado. E quanto CACurrentMediaTime()ao QuartzCore?
Cœur
1
Nota: também se pode usar em @import Darwin;vez de#include <mach/mach_time.h>
Cœur
@ Cœur CACurrentMediaTime deve ser exatamente equivalente ao meu código. Bom achado! (O cabeçalho diz "Este é o resultado da chamada de mach_absolute_time () e da conversão das unidades em segundos")
nevyn
"pode ​​mudar a qualquer momento devido a muitos motivos diferentes, como sincronização da hora da rede (NTP), atualização do relógio (geralmente ocorre para ajustar para desvio), ajustes de DST, segundos intercalados e assim por diante." - Que outras razões podem ser? Estou observando saltos para trás de cerca de 50 ms sem conexão com a Internet. Então, aparentemente, nenhum desses motivos se aplica ...
Falko
@Falko não tenho certeza, mas se eu tivesse que adivinhar, apostaria que o sistema operacional faz a compensação de deriva por conta própria se notar diferentes relógios de hardware fora de sincronia.
nevyn
14

CFAbsoluteTimeGetCurrent()retorna o tempo absoluto como um doublevalor, mas não sei qual é sua precisão - ele pode atualizar apenas a cada dúzia de milissegundos ou pode atualizar a cada microssegundo, não sei.

Adam Rosenfield
fonte
2
É um valor de ponto flutuante de precisão dupla, porém, e fornece precisão de submilissegundos. Um valor de 72,89674947369 segundos não é incomum ...
Jim Dovey
25
@JimDovey: Eu diria que um valor de 72.89674947369segundos seria bastante incomum, considerando todos os outros valores que poderia ser. ;)
FreeAsInBeer
3
@Jim: Você tem alguma citação que forneça uma precisão inferior a um milissegundo (esta é uma pergunta honesta)? Espero que todos aqui entendam as diferenças entre exatidão e precisão .
Adam Rosenfield
5
CFAbsoluteTimeGetCurrent () chama gettimeofday () no OS X e GetSystemTimeAsFileTime () no Windows. Aqui está o código-fonte .
Jim Dovey
3
Ah, e gettimeofday () é implementado usando o temporizador de nanossegundos Mach via mach_absolute_time () ; aqui está a fonte para a implementação de página comum de gettimeofday () em Darwin / ARM: opensource.apple.com/source/Libc/Libc-763.12/arm/sys/…
Jim Dovey
10

Eu NÃO usaria mach_absolute_time()porque ele consulta uma combinação do kernel e do processador por um tempo absoluto usando ticks (provavelmente um tempo de atividade).

O que eu usaria:

CFAbsoluteTimeGetCurrent();

Esta função é otimizada para corrigir a diferença no software e hardware iOS e OSX.

Algo mais geek

O quociente de uma diferença em mach_absolute_time()e AFAbsoluteTimeGetCurrent()está sempre em torno de 24000011.154871

Aqui está um registro do meu aplicativo:

Por favor, note que o tempo resultado final é uma diferença em 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] -----------------------------------------------------
Nate Symer
fonte
Acabei usando mach_absolute_time()com um mach_timebase_info_datae depois usei (long double)((mach_absolute_time()*time_base.numer)/((1000*1000)*time_base.denom));. Para obter a base de tempo mach, você pode fazer(void)mach_timebase_info(&your_timebase);
Nate Symer
12
De acordo com os documentos de CFAbsoluteTimeGetCurrent (), "A hora do sistema pode diminuir devido à sincronização com referências de hora externas ou devido a uma alteração explícita do relógio pelo usuário." Não entendo porque é que alguém iria querer usar algo assim para medir o tempo decorrido, se pode retroceder.
Kristopher Johnson,
6
#define CTTimeStart() NSDate * __date = [NSDate date]
#define CTTimeEnd(MSG) NSLog(MSG " %g",[__date timeIntervalSinceNow]*-1)

Uso:

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

Resultado:

2013-08-23 15:34:39.558 App-Dev[21229:907] that was a long time: .0023
gngrwzrd
fonte
NSDatedepende do relógio do sistema que pode ser alterado a qualquer momento, resultando potencialmente em intervalos de tempo errados ou mesmo negativos. Em mach_absolute_timevez disso, use para obter o tempo decorrido correto.
Cœur
mach_absolute_timepode ser afetado por reinicializações do dispositivo. Em vez disso, use o horário do servidor.
kelin
5

Além disso, aqui está como calcular um 64 bits NSNumberinicializado com a época Unix em milissegundos, caso seja assim que você deseja armazená-lo no CoreData. Eu precisava disso para meu aplicativo que interage com um sistema que armazena datas dessa forma.

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

As funções baseadas em mach_absolute_timesão boas para medições curtas.
Mas, para medições longas, a advertência importante é que elas param de fazer tique-taque enquanto o dispositivo está dormindo.

Existe uma função para obter o tempo desde o boot. Não para durante o sono. Além disso, gettimeofdaynão é monotônico, mas em meus experimentos sempre vi que o tempo de inicialização muda quando o tempo do sistema muda, então acho que deve funcionar bem.

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
}

E desde iOS 10 e macOS 10.12, podemos usar 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
    }
}

Resumindo:

  • Date.timeIntervalSinceReferenceDate - muda quando a hora do sistema muda, não monotônica
  • CFAbsoluteTimeGetCurrent() - não monotônico, pode retroceder
  • CACurrentMediaTime() - para de funcionar quando o dispositivo está em repouso
  • timeSinceBoot() - não dorme, mas pode não ser monotônico
  • CLOCK_MONOTONIC - não dorme, monotônico, compatível desde iOS 10
Pavel Alexeev
fonte
1

Eu sei que este é antigo, mas até eu me vi vagando por ele novamente, então pensei em apresentar minha própria opção aqui.

A melhor aposta é verificar minha postagem no blog sobre isto: Cronometrando coisas em Objective-C: um cronômetro

Basicamente, escrevi uma classe que para de assistir de uma maneira muito básica, mas é encapsulada de forma que você só precisa fazer o seguinte:

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

E você acaba com:

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

no log ...

Novamente, confira minha postagem para um pouco mais ou faça o download aqui: MMStopwatch.zip

bladnman
fonte
Sua implementação não é recomendada. NSDatedepende do relógio do sistema que pode ser alterado a qualquer momento, resultando potencialmente em intervalos de tempo errados ou mesmo negativos. Em mach_absolute_timevez disso, use para obter o tempo decorrido correto.
Cœur
1

Você pode obter a hora atual em milissegundos desde 1º de janeiro de 1970 usando um NSDate:

- (double)currentTimeInMilliseconds {
    NSDate *date = [NSDate date];
    return [date timeIntervalSince1970]*1000;
}
Camilo Ortegón
fonte
Isso lhe dá tempo em milissegundos, mas ainda na precisão de segundos
dev
-1

Para aqueles, precisamos da versão Swift da resposta de @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

Espero que isso lhe ajude.

Victor Sigler
fonte
2
NSDatedepende do relógio do sistema que pode ser alterado a qualquer momento, resultando potencialmente em intervalos de tempo errados ou mesmo negativos. Em mach_absolute_timevez disso, use para obter o tempo decorrido correto.
Cœur