7

Sto provando a implementare un cronometro basato sul modello MVC.Modello di osservatore per il cronometro

Il cronometro utilizza NSTimer con il selettore -(void) tick chiamato ogni timeout.

Ho provato a fare del cronometro un modello per la riusabilità, ma ho riscontrato alcuni problemi di progettazione relativi all'aggiornamento del controller di visualizzazione per ogni spunta.

Per prima cosa ho creato un protocollo con il metodo tick e reso il controller di visualizzazione suo delegato. Il controller della vista aggiorna quindi le viste in base alle proprietà del timer ad ogni spunta. elapsedTime è un NSTimeInterval di sola lettura.

Funziona, ma penso che potrebbe essere un cattivo design. Sono un principiante Objective-C/Cocoa Touch. Dovrei usare qualcosa come KVO? O c'è una soluzione più elegante per il modello per notificare al controller di visualizzazione che è stato modificato elapsedTime?

+1

Bella prima domanda! Benvenuto in SO! –

+0

Qual è esattamente la relazione tra il timer e il controller di visualizzazione? Il timer è di proprietà di VC? –

+0

Grazie :) Il Timer è di proprietà del VC, sì. Ho implementato un IntervalTimer che eredita da Timer e quindi il VC possiede invece IntervalTimer - l'IntervalTimer è in realtà quello che mi dà un po 'di problemi. – Jach0

risposta

0

Ultimamente, sto usando i blocchi invece dei semplici vecchi @selector. Crea meglio e codifica e mantiene la logica nella stessa posizione.

Non c'è nessun blocco supporto nativo a NSTimer, ma ho usato una categoria dal https://gist.github.com/250662/d4f99aa9bde841107622c5a239e0fc6fa37cb179

Senza il selettore di ritorno, si mantiene il codice in un punto:

__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

Il timer è un buon modo per assicurati di aggiornare periodicamente l'interfaccia utente, ma non utilizzarla per tenere traccia del tempo. NSTimer can drift e eventuali piccoli errori possono accumularsi se si utilizza un timer per accumulare secondi.

Invece, utilizzare NSTimer per attivare un metodo che aggiorna l'interfaccia utente, ma ottenere il tempo reale utilizzando NSDate. NSDate ti darà una risoluzione in millisecondi; se hai davvero bisogno di meglio, prendi in considerazione lo this suggestion to use Mach's timing functions. Quindi, utilizzando NSDate, il codice potrebbe essere qualcosa di simile:

- (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]; 
} 

Il codice potrebbe essere un po 'più sofisticato se si consente il riavvio, ecc, ma la cosa importante è che non si sta usando NSTimer a misurare il tempo trascorso totale.

Troverete ulteriori informazioni utili in this SO thread.

2

Vorrei raccomandare contro KVO per questo problema. Introduce molta complessità (e diversi fastidiosi trucchi) per un piccolo vantaggio qui. KVO è importante nei casi in cui è necessario garantire un sovraccarico assolutamente minimo. Apple lo usa molto nei casi per oggetti di basso livello e ad alte prestazioni come i livelli. È l'unica soluzione generalmente disponibile che offre zero-overhead quando non c'è osservatore. Il più delle volte, non ne hai bisogno. Gestire correttamente KVO può essere complicato e i bug che può creare sono fastidiosi da rintracciare.

Non c'è niente di sbagliato nell'approccio dei delegati. È corretto MVC. L'unica cosa di cui devi davvero preoccuparti è che NSTimer non fa forti promesse su quando viene chiamato. In alcuni casi è persino possibile saltare un timer ripetuto. Per evitare questo problema, in genere si desidera calcolare elapsedTime in base all'orario corrente anziché incrementarlo. Se il timer può mettere in pausa, è necessario mantenere un accumulatore e una data "quando ho l'ultimo avvio".

Se sono necessari timer con maggiore accuratezza o con costi inferiori, è possibile consultare dispatch_source_set_timer(), ma per un semplice cronometro a bersaglio umano, NSTimer va bene e una scelta eccellente per un progetto semplice.

+0

Sono a conoscenza del problema con il timer di salto e utilizzando l'incremento. Sto usando NSDate e in setElapsedTime I confronta NSDate (+ un offset quando viene usata la pausa) – Jach0

+0

Sto provando ad implementare un timer per il conto alla rovescia che eredita dal cronometro e ha alcuni attributi extra inclusi workInterval e restInterval, ma le etichette in Il mio ViewController viene aggiornato un po 'strano. Ho pensato che potrebbe avere qualcosa a che fare con le chiamate in sequenza tra il segno di spunta del ViewController, il segno di spunta Timers e il segno di spunta IntervalTimers. – Jach0

Problemi correlati