7

Próbuję zaimplementować stoper oparty na modelu MVC.Wzorzec obserwatora dla stopera

Stoper używa NSTimer z selektorem -(void) tick nazywane każdym timeout.

Próbowałem ustawić stoper jako model ponownego użycia, ale pojawiły się problemy z projektowaniem dotyczące aktualizacji kontrolera widoku dla każdego tiku.

Najpierw utworzyłem protokół za pomocą metody tick i ustawiłem kontroler widoku na jego delegata. Następnie kontroler widoku aktualizuje widoki w oparciu o właściwości timera przy każdym tiku. elapsedTime jest tylko NSTimeInterval w trybie tylko do odczytu.

Działa, ale myślę, że to może być zły projekt. Jestem początkującym użytkownikiem Objective-C/Cocoa Touch. Czy powinienem używać czegoś takiego jak KVO? Czy istnieje bardziej eleganckie rozwiązanie dla modelu, aby powiadomić kontroler widoku, że zmieniono elapsedTime?

+1

Ładne pierwsze pytanie! Witamy w SO! –

+0

Jaki jest dokładnie związek między zegarem i kontrolerem widoku? Czy licznik jest własnością VC? –

+0

Dziękuję :) Timer jest własnością VC, tak. Zaimplementowałem IntervalTimer, który dziedziczy z Timera, a następnie VC posiada IntervalTimer - IntervalTimer jest tym, który daje mi trochę kłopotu. – Jach0

Odpowiedz

0

Ostatnio używam bloków zamiast zwykłego starego @selector. Tworzy lepiej i kodu i utrzymuje logikę w tej samej lokalizacji.

Nie ma natywne bloki wspierać w NSTimer, ale kiedyś kategorię z https://gist.github.com/250662/d4f99aa9bde841107622c5a239e0fc6fa37cb179

Bez selektora powrotnej, zachować kod w jednym miejscu:

__block int seconds = 0; 
    NSTimer* timer = [NSTimer scheduledTimerWithTimeInterval:1 
               repeats:YES 
               usingBlock:^(NSTimer *timer) { 

               seconds++; 
               // Update UI 

               if (seconds>=60*60*2) { 
                [timer invalidate]; 
               } 




}]; 
5

Timer jest dobrym sposobem pamiętaj, aby okresowo aktualizować interfejs użytkownika, ale nie używaj go do śledzenia czasu. NSTimer can drift, a wszelkie małe błędy mogą się kumulować, jeśli używasz stopera do gromadzenia sekund.

Zamiast tego użyj NSTimer, aby uruchomić metodę, która aktualizuje twój interfejs użytkownika, ale uzyskaj czas rzeczywisty za pomocą NSDate. NSDate da ci rozdzielczość w milisekundach; jeśli naprawdę potrzebujesz czegoś lepszego, rozważ this suggestion to use Mach's timing functions. Tak więc, przy użyciu NSDate, Twój kod może być coś takiego:

- (IBAction)startStopwatch:(id)sender 
{ 
    self.startTime = [NSDate date]; 
    self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1 
                target:self 
               selector:@selector(tick:) 
               userInfo:repeats:YES]; 
} 

- (void)tick:(NSTimer*)theTimer 
{ 
    self.elapsedTime = [self.startTime timeIntervalSinceNow]; 
    [self updateDisplay]; 
} 

- (IBAction)stopStopwatch:(id)sender 
{ 
    [self.timer invalidate]; 
    self.timer = nil; 
    self.elapsedTime = [self.startTime timeIntervalSinceNow]; 
    [self updateDisplay]; 
} 

Twój kod może być trochę bardziej skomplikowane, jeśli pozwalają na ponowne uruchomienie, itd., Ale najważniejsze jest to, że nie używasz NSTimer do zmierzyć całkowity czas, jaki upłynął.

Znajdziesz dodatkowe pomocne informacje w this SO thread.

2

Polecam przeciw KVO dla tego problemu. Wprowadza dużo złożoności (i kilku denerwujących bzdur) dla małych korzyści. KVO jest ważne w przypadkach, w których trzeba zapewnić absolutnie minimalny narzut. Apple używa go bardzo często w przypadku obiektów niskiego poziomu o wysokiej wydajności, takich jak warstwy. Jest to jedyne ogólnie dostępne rozwiązanie, które oferuje zero-narzut, gdy nie ma obserwatora. Przez większość czasu nie potrzebujesz tego. Właściwe obchodzenie się z KVO może być trudne, a błędy, które może stworzyć, są denerwujące do wyśledzenia.

Nie ma nic złego w swoim podejściu delegatów. To jest poprawne MVC. Jedyne, o co naprawdę musisz się martwić, to to, że NSTimer nie składa obiecanych obietnic, kiedy zostanie wywołany. Powtarzający się zegar jest nawet w niektórych przypadkach pomijany. Aby uniknąć tego problemu, na ogół chcesz obliczyć wartość elapsedTime na podstawie bieżącego czasu, a nie przez jego zwiększenie. Jeśli timer może się zatrzymać, musisz zachować akumulator i datę "kiedy miałam ostatni start".

Jeśli potrzebujesz zegarów o wyższej dokładności lub tańszych kosztach, możesz spojrzeć na dispatch_source_set_timer(), ale dla prostego, przeznaczonego dla ludzi stopera, NSTimer jest w porządku i jest doskonałym wyborem dla prostego projektu.

+0

Jestem świadomy problemu z zegarem pomijania i użyciem inkrementacji. Używam NSDate i setElapsedTime Porównuję NSDates (+ odsunięcie, gdy pauza jest używana) – Jach0

+0

Próbuję zaimplementować interwałowy licznik czasu, który dziedziczy ze stopera i ma pewne dodatkowe atrybuty, w tym workInterval i restInterval, ale etykiety w mój ViewController jest trochę dziwny. Pomyślałem, że może to mieć coś wspólnego z sekwencyjnymi wywołaniami między znacznikiem ViewController, znacznikiem Timers i znacznikiem IntervalTimers. – Jach0

Powiązane problemy