Obtendo o tempo decorrido no Objective-C

153

Preciso calcular o tempo decorrido entre dois eventos, por exemplo, a aparência de um UIView e a primeira reação do usuário.

Como posso alcançá-lo no Objective-C?

Ilya Suzdalnitski
fonte

Respostas:

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

timeInterval é a diferença entre iniciar e agora, em segundos, com precisão abaixo de milissegundos.

Can Berk Güder
fonte
18
@NicolasZozol, você pode usar fabs(...)para obter o valor absoluto de um flutuador. Por exemplo. NSTimeInterval timeInterval = fabs([start timeIntervalSinceNow]);
So Over It
1
parece que o valor de timeInterval é negativo.
Jacky
se você receber um valor negativo - se multiplicam com -1 e você é bom
PinkFloydRocks
10
Contar com isso [NSDate date]pode levar a erros de rastreamento difíceis, consulte esta resposta para obter mais informações.
Senseful
@PinkFloydRocks - O quê? Como um valor negativo jamais poderia ocorrer legitimamente?
Todd Lehman
228

Você não deve confiar [NSDate date]para fins de tempo, pois isso pode subestimar ou subnotificar o tempo decorrido. Existem até casos em que seu computador aparentemente viaja no tempo, já que o tempo decorrido será negativo! (Por exemplo, se o relógio retroceder durante o tempo.)

De acordo com Aria Haghighi na palestra "Reconhecimento avançado de gestos para iOS" do curso de inverno de 2013 em Stanford para iOS (34:00), você deve usar CACurrentMediaTime()se precisar de um intervalo de tempo preciso.

Objetivo-C:

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

Rápido:

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

O motivo é que a [NSDate date]sincronização no servidor pode levar a "soluços de sincronização de tempo", o que pode levar a erros muito difíceis de rastrear. CACurrentMediaTime(), por outro lado, é um horário do dispositivo que não muda com essas sincronizações de rede.

Você precisará adicionar a estrutura QuartzCore às configurações do seu alvo.

Sensível
fonte
17
Voto por favor. Embora eu pense que todos podem concordar que o NSDate deve ser sua primeira opção, essa sugestão resolveu um problema meu. Ao chamar [NSDate date]dentro de um bloco de conclusão dentro de um bloco de despacho, eu estava recebendo o mesmo horário "atual" relatado [NSDate date]imediatamente antes da execução atingir o ponto em que meus blocos foram criados. CACurrentMediaTime()resolveu esse problema.
N00neimp0rtant
Como usaríamos o tempo decorrido para exibição como mm: ss?
CyberMew
@ CyberMew: Esse é um problema separado. Consulte Como decomponho um NSTimeInterval em ano, meses, dias, horas, minutos e segundos no iPhone? para algumas soluções.
Sensible 22/09/14
12
Esteja ciente de que CACurrentMediaTime()para de marcar quando o dispositivo entra no modo de suspensão. Se você testar com o dispositivo desconectado de um computador, bloqueie-o e aguarde ~ 10 minutos para descobrir que CACurrentMediaTime()não acompanha o relógio de parede.
russbishop
adicionar e importação #import <QuartzCore / QuartzCore.h>
steveen zoleko
23

Use o timeIntervalSinceDatemétodo

NSTimeInterval secondsElapsed = [secondDate timeIntervalSinceDate:firstDate];

NSTimeIntervalé apenas a double, defina NSDateassim:

typedef double NSTimeInterval;
Marco Lazzeri
fonte
15

Para quem vem aqui procurando uma implementação getTickCount () para iOS, aqui está o meu depois de reunir várias fontes.

Anteriormente, eu tinha um bug neste código (dividido por 1000000 primeiro), que estava causando uma quantização da saída no meu iPhone 6 (talvez esse não fosse um problema no iPhone 4 / etc ou simplesmente nunca percebi). Observe que, ao não executar essa divisão primeiro, há algum risco de estouro se o numerador da base de tempo for bastante grande. Se alguém estiver curioso, há um link com muito mais informações aqui: https://stackoverflow.com/a/23378064/588476

À luz dessa informação, talvez seja mais seguro usar a função da Apple CACurrentMediaTime!

Também mach_timebase_infocomparei a chamada e ela leva aproximadamente 19ns no meu iPhone 6, então removi o código (não thread-safe) que estava armazenando em cache a saída dessa chamada.

#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;
}

Esteja ciente do risco potencial de estouro, dependendo da saída da chamada da base de tempo. Eu suspeito (mas não sei) que pode ser uma constante para cada modelo de iPhone. no meu iPhone 6 era 125/3.

A solução usando CACurrentMediaTime()é bastante trivial:

uint64_t getTickCount(void)
{
    double ret = CACurrentMediaTime();
    return ret * 1000;
}
Wayne Uroda
fonte
Alguém sabe por que há uma conversão redundante (nula) na chamada mach_timebase_info?
Simon Tillson
@SimonTillson, é uma boa pergunta - ou foi necessário silenciar um aviso ou copiei-o de outro lugar e não notei excluí-lo. Não me lembro.
Wayne Uroda 12/12
2
Este não é um tópico seguro. mach_timebase_info()redefinirá o passado mach_timebase_info_data_tpara {0,0}antes de defini-lo para os valores reais, o que pode causar divisão por zero se o código for chamado de vários threads. Use em dispatch_once()vez disso (ou CACurrentMediaTimeque seja apenas o tempo de conversão de segundos para segundos).
wonder.mice
Graças @ wonder.mice, eu atualizei a minha resposta (encontrado uma questão relativa quantização bem, eu culpo-6-ano mais novo me ...)
Wayne Uroda
4

use a função timeIntervalSince1970 da classe NSDate como abaixo:

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

Basicamente, é isso que eu uso para comparar a diferença em segundos entre duas datas diferentes. verifique também este link aqui

Raj
fonte
0

As outras respostas estão corretas (com uma ressalva *). Eu adiciono esta resposta simplesmente para mostrar um exemplo de uso:

- (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.
}

No console do depurador, você vê algo assim:

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

* Advertência: Como outros mencionados, use NSDatepara calcular o tempo decorrido apenas para fins casuais. Um desses objetivos pode ser o teste comum, o perfil bruto, no qual você deseja apenas uma idéia aproximada de quanto tempo um método está demorando.

O risco é que a configuração da hora atual do relógio do dispositivo possa mudar a qualquer momento devido à sincronização do relógio da rede. Portanto, o NSDatetempo pode pular para frente ou para trás a qualquer momento.

Basil Bourque
fonte