2013-07-01 17 views
6

Próbuję użyć NSTimer stworzyć stoper licznik sumujący stylu co 0,1 sekundy, ale wydaje się, że działa zbyt szybko, czasami ..Dokładność NSTimer

ten sposób robiłem go:

Timer =[NSTimer scheduledTimerWithTimeInterval: 0.1 target:self selector:@selector(updateTimeLabel) userInfo:nil repeats: YES]; 

, a następnie:

-(void)updateTimeLabel 
{ 
    maxTime=maxTime+0.1; 
    timerLabel.text =[NSString stringWithFormat:@"%.1f Seconds",maxTime]; 
} 

Spowoduje to wyświetlenie wartości timera na etykiecie, a później mogę wykorzystać MaxTime jako czas, gdy stoper jest zatrzymany ...

Problem polega na tym, że działa bardzo niedokładnie.

Czy istnieje metoda, dzięki której mogę upewnić się, że NSTimer strzela dokładnie co 0,1 sekundy dokładnie? Wiem, że NSTimer nie jest dokładny i proszę o poprawkę, żeby była dokładna.

THanks

+0

W jakiej metody tworzysz timer? –

+2

Jeśli występują zdarzenia czasowe, należy wywołać 'CFAbsoluteTimeGetCurrent()' po uruchomieniu licznika czasu, a następnie wywołać ponownie pod koniec interwału. Odejmij 2 wartości, a otrzymasz czas, który upłynął w sekundach. – nielsbot

+0

tj. Rzeczywisty czas, który upłynął, jest niezależny od czasu, który wyświetlasz. więc możesz aktualizować swój wyświetlacz mniej więcej co 0,1 sekundy, ale nie ma to znaczenia, jeśli twoje aktualizacje są trochę za małe. Następnie, gdy zegar zostanie zatrzymany, zaktualizuj ekran ponownie, używając metody z mojego ostatniego komentarza. – nielsbot

Odpowiedz

5

Oto klasy można użyć, aby robić to, co chcesz:

@interface StopWatch() 
@property (nonatomic, strong) NSTimer * displayTimer ; 
@property (nonatomic) CFAbsoluteTime startTime ; 
@end 

@implementation StopWatch 

-(void)dealloc 
{ 
    [ self.displayTimer invalidate ] ; 
} 

-(void)startTimer 
{ 
    self.startTime = CFAbsoluteTimeGetCurrent() ; 
    self.displayTimer = [ NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(timerFired:) userInfo:nil repeats:YES ] ; 
} 

-(void)stopTimer 
{ 
    [ self.displayTimer invalidate ] ; 
    self.displayTimer = nil ; 

    CFAbsoluteTime elapsedTime = CFAbsoluteTimeGetCurrent() - self.startTime ; 
    [ self updateDisplay:elapsedTime ] ; 
} 

-(void)timerFired:(NSTimer*)timer 
{ 
    CFAbsoluteTime elapsedTime = CFAbsoluteTimeGetCurrent() - self.startTime ; 
    [ self updateDisplay:elapsedTime ] ; 
} 

-(void)updateDisplay:(CFAbsoluteTime)elapsedTime 
{ 
    // update your label here 
} 

@end 

Kluczowe punkty to:

  1. zrobić swój czas przez zapisanie czasu systemowego, gdy stoper zostanie uruchomiony w zmiennej.
  2. po zatrzymaniu stopera, oblicz czas, który upłynął, odejmując czas rozpoczęcia stopu od bieżącego czasu.
  3. zaktualizuj wyświetlacz za pomocą minutnika. Nie ma znaczenia, czy zegar jest dokładny, czy nie. Jeśli próbujesz zagwarantować aktualizacje ekranu co najmniej co 0,1 s, możesz spróbować ustawić interwał czasomierza na 1/2 minimalnego czasu aktualizacji (0,05 s).
+0

Cześć Niels ... Wygląda na to, że działa, ale etykieta ma bardzo dużą wartość, mimo że użyłem wartości względnej dla upływającego czasu ... Jaki jest pomysł? – user2308343

+0

+1 Ale kilka myśli: 1. Nie można "unieważnić" licznika w 'dealloc', ponieważ silne odwołanie zegara uniemożliwi wywołanie' dealloc'. Masz tutaj silny cykl referencyjny.Powinieneś prawdopodobnie stworzyć timer w 'viewWillAppear' i' unieważnić' w 'viewWillDisappear'. 2. Mogę również zaproponować 'CADisplayLink' zamiast' NSTimer', ale idea jest taka sama. – Rob

+0

Innymi słowy (jak rozumiem) ... naprawdę nie muszę przekazywać "elapsedTime" do metody wyświetlania, ponieważ jest teraz wypalana dokładnie teraz? Mój początkowy updateTimeLabel nadal działałby, ponieważ aktualizacja jest w porządku przy użyciu bezwzględnej różnicy w timeFired – user2308343

8

zgodnie z dokumentacją NSTimer, to nie ma być dokładne.

Ponieważ z różnych źródeł wejściowych typowy pętla run zarządza, efektywna rozdzielczość przedziału czasowego dla czasomierza jest ograniczona do rzędu 50-100 milisekund. Jeśli czas wyzwalania timera wystąpi podczas długiego objaśnienia lub gdy pętla uruchamiania jest w trybie, który nie monitoruje timera, timer nie uruchamia się, dopóki następnym razem pętla nie sprawdzi licznika czasu. W związku z tym rzeczywisty czas, w którym licznik czasu uruchamia się potencjalnie, może być znacznym okresem po zaplanowanym czasie wystrzelenia.

Możesz użyć funkcji dispatch_after z GCD, co jest sugerowane przez official documentation o dokładnym tego celu (tworząc timer).

Jeśli chcesz wykonać blok raz po określonym przedziale czasu, można użyć funkcji dispatch_after lub dispatch_after_f.


Nawiasem mówiąc, zgadzam się z odpowiedzią Caleba. Prawdopodobnie rozwiążesz swoje problemy, jeśli nie narobisz błędu jak teraz. Jeśli przechowujesz datę początkową i przeliczasz czas w każdej iteracji za pomocą metody -timeIntervalSince:, otrzymasz dokładną aktualizację interfejsu użytkownika, niezależnie od dokładności zegara.

+0

Gabriele - Nie potrzebuję "czasu od", ponieważ muszę wyświetlać czas w sposób ciągły użytkownikowi na etykiecie ... – user2308343

+2

Potrzebujesz go, aby nie narobić błędu. Jeśli przeliczysz czas przy każdej "iteracji", błąd nie zostanie skumulowany. W przeciwnym razie sumujesz każdą niedokładność 'NSTimer', kończąc na bardzo nieprecyzyjnej miary. –

+4

NSTimer nie jest "nieprecyzyjny", jeśli używasz powtórzenia. –

2

NSTimer nie gwarantuje dokładności, chociaż w praktyce jest to zwykle (jeśli nie robisz nic więcej w głównym wątku ...). Jednak jest całkowicie uzasadnione, aby zaktualizować wyświetlacz ... po prostu nie używaj wywołania zwrotnego, aby obliczyć licznik czasu. Zapisz aktualny czas po uruchomieniu timera i uzyskaj różnicę między teraz a kiedy zaczniesz za każdym razem, gdy timer się uruchomi. Wtedy nie ma znaczenia, jak dokładnie NSTimer strzela, wpływa tylko na to, ile razy na sekundę twój ekran na ekranie się aktualizuje.

4
maxTime=maxTime+0.1; 

To jest zła droga. Nie chcesz używać timera do gromadzenia upływu czasu, ponieważ wraz z nim będziesz gromadził błędy. Użyj timera, aby okresowo wyzwalać metodę, która oblicza upływający czas, używając NSDate, a następnie aktualizuje wyświetlacz.Tak, zmienić swój kod do zrobienia czegoś w zamian:

maxTime = [[NSDate date] timeIntervalSince:startDate]; 
+0

ale przypuszczam, że wyzwalacz NSTimer jest tym, co dzieje się co 0,1 sekundy, i zwiększa maxTime - maxTime podąża za timerem i go nie ustawia – user2308343

+3

Rozumiem, ale timer może być wyłączony o 50ms w jedną stronę lub inny. To nie jest duży błąd dla wielu rzeczy, ale szybko się zsumuje. Nikt nie przejmuje się tym, czy aktualizujesz ekran * dokładnie * co 0,1 sekundy, ale kiedy * zrobisz *, aby zaktualizować wyświetlacz, powinien on być tak dokładny, jak to tylko możliwe. Osiągasz to przez obliczanie na podstawie różnicy między bieżącym czasem i ustalonym czasem rozpoczęcia, co pozwala uniknąć nagromadzenia błędu. – Caleb