2012-05-23 14 views
5

Szukam, aby utworzyć licznik czasu dla SMPTE Timecode (HH: MM: SS: FF) na iOS. Zasadniczo jest to tylko odliczający czasomierz o rozdzielczości 33.33333ms. Nie jestem pewien, czy NSTimer jest na tyle dokładny, że można na nim liczyć, by stworzyć ten timer. Chciałbym wystrzelić zdarzenie lub wywołać fragment kodu za każdym razem, gdy ten zegar się zwiększy/zmniejszy.Jak utworzyć dokładne zdarzenie licznika czasu w Objective-C/iOS?

Jestem nowy w Objective-C, więc szukam mądrości od społeczności. Ktoś zasugerował klasę CADisplayLink, szukając porady eksperta.

Odpowiedz

12

Wypróbuj program CADisplayLink. Wystrzeliwuje z częstotliwością odświeżania (60 fps).

CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(timerFired:)]; 
displayLink.frameInterval = 2; 
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 

Spowoduje to uruchomienie co 2 klatek, czyli 30 razy na sekundę, co wydaje się być tym, na co masz ochotę.

Pamiętaj, że jest to związane z przetwarzaniem ramki wideo, więc musisz bardzo szybko wykonać swoją pracę z powrotem.

+0

Należy zauważyć, że element _frameInterval_ był nieaktualny w systemie iOS 10.0, użył _preferredFramesPerSecond_, tak jak poniżej: 'displayLink.preferredFramesPerSecond = 30.0;' dla 30 klatek na sekundę – WongWray

1

Jeśli kierowania iOS 4+, można użyć Grand Central Dispatch:

// Set the time, '33333333' nanoseconds in the future (33.333333ms) 
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 33333333); 
// Schedule our code to run 
dispatch_after(time, dispatch_get_main_queue(), ^{ 
    // your code to run here... 
}); 

To wywoła ten kod po 33.333333ms. Jeśli to ma być kontrakt pętla sorta, możesz zamiast tego wykorzystuje wskaźnik funkcji zamiast bloku użyć funkcji dispatch_after_f:

void DoWork(void *context); 

void ScheduleWork() { 
    // Set the time, '33333333' nanoseconds in the future (33.333333ms) 
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 33333333); 
    // Schedule our 'DoWork' function to run 
    // Here I pass in NULL for the 'context', whatever you set that to will 
    // get passed to the DoWork function 
    dispatch_after_f(time, dispatch_get_main_queue(), NULL, &DoWork); 
} 

void DoWork(void *context) { 
    // ... 
    // Do your work here, updating an on screen counter or something 
    // ... 

    // Schedule our DoWork function again, maybe add an if statement 
    // so it eventually stops 
    ScheduleWork(); 
} 

a potem po prostu zadzwonić ScheduleWork();, gdy chcesz, aby uruchomić stoper. W przypadku pętli powtarzalnej osobiście uważam, że jest to trochę czystsze niż powyższa metoda blokowania, ale dla zadania jednorazowego zdecydowanie preferuję metodę blokowania.

Aby uzyskać więcej informacji, patrz Grand Central Dispatch docs.

+1

Nie sądzę, że 'dispatch_after' będzie bardziej precyzyjny niż' NSTimer'. W rzeczywistości, w iOS 4 jestem całkiem pewny, że ten ostatni jest zaimplementowany pod względem tego pierwszego. – benzado

+0

Myślę, że zaplanowanie nowego wątku za każdym razem nałoży jakieś nieścisłości, ale przyznam, że tak naprawdę nie przetestowałem go. IOS może mieć niewielki narzut, warto spróbować. –

4

Po prostu nie masz żadnych gwarancji z NSTimer lub dispatch_after; Planują kod uruchamiany w głównym wątku, ale jeśli wykonanie i zablokowanie głównego wątku potrwa zbyt długo, twój timer się nie uruchomi.

Powiedziawszy, możesz łatwo uniknąć blokowania głównego wątku (używaj tylko asynchronicznych operacji we/wy) i rzeczy powinny być całkiem dobre.

Nie mówisz dokładnie, co musisz zrobić w kodzie timera, ale jeśli wszystko, co musisz zrobić, to wyświetlić odliczanie, powinieneś być w porządku, o ile obliczasz czas SMPTE w oparciu o czas systemowy, a nie liczba sekund, które Twoim zdaniem powinny upłynąć w oparciu o interwał czasomierza. Jeśli to zrobisz, prawie na pewno będziesz dryfować i tracić zsynchronizowanie z rzeczywistym czasem. Zamiast tego, należy pamiętać, czas rozpoczęcia, a następnie zrobić wszystko, matematyki w oparciu o które:

// Setup 
timerStartDate = [[NSDate alloc] init]; 
[NSTimer scheduledTimer... 

- (void)timerDidFire:(NSTimer *)timer 
{ 
    NSTImeInterval elapsed = [timerStartDate timeIntervalSinceNow]; 
    NSString *smtpeCode = [self formatSMTPEFromMilliseconds:elapsed]; 
    self.label.text = smtpeCode; 
} 

Teraz będzie wyświetlany poprawny kod czasu bez względu na to, jak często timer jest zwolniony. (Jeśli licznik czasu nie wystrzeli wystarczająco często, licznik czasu nie zaktualizuje się, ale gdy zostanie zaktualizowany, będzie dokładny, nigdy nie będzie się zsynchronizować.)

Jeśli używasz programu CADisplayLink, twoja metoda zostanie nazwana tak szybko, jak aktualizacja ekranu. Innymi słowy, tak szybko, jak byłoby to przydatne, ale nie szybciej. Jeśli wyświetlasz czas, prawdopodobnie jest to właściwy sposób.

+0

Dzięki! Ponieważ będę to wykorzystywał jako podstawę do dekompresji ramek wideo, nie mogę użyć NSTimer do niezawodnego sterowania zegarem. Bardziej prawdopodobne jest to, że CADisplayLink jest do zrobienia, a ja zablokuję go do maksymalnej szybkości (33 ms), tak aby rysował ramki nie więcej niż 33 ms od siebie, ale mniej, jeśli dekompresja trwa zbyt długo. –

+0

Użyłem tego podejścia z dispatch_after i działa doskonale na moje potrzeby. Dzięki! –

Powiązane problemy