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.
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.
Respostas:
NSDate
e os timeIntervalSince*
métodos retornarão a NSTimeInterval
que é 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 NSDate
encontrada em Referência de classe NSDate .
NSDate
e mach_absolute_time()
em cerca de 30ms de nível. 27 vs. 29, 36 vs. 39, 43 vs. 45. NSDate
foi mais fácil de usar para mim e os resultados foram semelhantes o suficiente para não me importar.
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.)
CACurrentMediaTime()
que converte mach_absolute_time()
diretamente em um double
.
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.
Por favor, não use NSDate
, CFAbsoluteTimeGetCurrent
ou gettimeofday
para 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
CACurrentMediaTime()
ao QuartzCore?
@import Darwin;
vez de#include <mach/mach_time.h>
CFAbsoluteTimeGetCurrent()
retorna o tempo absoluto como um double
valor, 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.
72.89674947369
segundos seria bastante incomum, considerando todos os outros valores que poderia ser. ;)
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] -----------------------------------------------------
mach_absolute_time()
com um mach_timebase_info_data
e 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);
#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
NSDate
depende 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_time
vez disso, use para obter o tempo decorrido correto.
mach_absolute_time
pode ser afetado por reinicializações do dispositivo. Em vez disso, use o horário do servidor.
Além disso, aqui está como calcular um 64 bits NSNumber
inicializado 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];
}
As funções baseadas em mach_absolute_time
sã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, gettimeofday
nã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(¤tTime, &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ônicaCFAbsoluteTimeGetCurrent()
- não monotônico, pode retrocederCACurrentMediaTime()
- para de funcionar quando o dispositivo está em repousotimeSinceBoot()
- não dorme, mas pode não ser monotônicoCLOCK_MONOTONIC
- não dorme, monotônico, compatível desde iOS 10Eu 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
NSDate
depende 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_time
vez disso, use para obter o tempo decorrido correto.
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;
}
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.
NSDate
depende 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_time
vez disso, use para obter o tempo decorrido correto.