Watch Pattern for Stopwatch

I am trying to implement a stopwatch based on the MVC model.

The stopwatch uses NSTimer, with the -(void) tick selector being called every timeout.

I tried to make a stopwatch as a model for reuse, but I had some design issues regarding how to update the view controller for each tick.

First, I created a protocol with a tick method and made the view controller its delegate. The view controller then updates the views based on the timer properties at each tick. elapsedTime is a read-only NSTimeInterval.

This works, but I think it might be a bad design. I am new to Objective-C / Cocoa. Should I use something like KVO? Or is there a more elegant solution for the model to notify the view controller that elapsedTime has changed?

+7
source share
3 answers

A timer is a good way to periodically update your user interface, but do not use it to track time. NSTimer can drift , and any small errors can accumulate if you use a timer to accumulate seconds.

Instead, use NSTimer to run a method that updates your user interface, but get real-time using NSDate. NSDate will give you resolution in milliseconds; if you’re really better, consider using the clock synchronization features . Thus, using NSDate, your code might look something like this:

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

Your code may be a little more complicated if you allow restarting, etc., but it is important here that you do not use NSTimer to measure the total elapsed time.

You will find additional useful information in this SO thread .

+5
source

I would recommend against KVO for this problem. It introduces a lot of complexity (and a few annoying bugs) for little benefit here. KVO is important when you need to ensure minimal overhead. Apple uses it a lot in cases for low-level high-performance objects such as layers. This is the only publicly available solution that offers zero overhead when there is no observer. Most of the time you do not need it. The proper handling of KVO can be complex, and the errors that it can create are annoying to track.

There is nothing wrong with your approach to delegates. This is correct MVC. The only thing you need to really worry about is that NSTimer does not make strong promises about when it called. The repeating timer is even allowed to skip in some cases. To avoid this problem, you usually want to calculate elapsedTime based on the current time, rather than increasing it. If the timer can stop, you need to save the battery and the date "when was the last time".

If you need timers with higher accuracy or lower cost, you can look at dispatch_source_set_timer() , but for a simple target-oriented stopwatch, NSTimer excellent, and an excellent choice for a simple project.

+2
source

Recently, I have been using blocks instead of the usual old @selector . It creates better code and stores logic in the same place.

There is no support for embedded blocks in NSTimer , but I used the category from https://gist.github.com/250662/d4f99aa9bde841107622c5a239e0fc6fa37cb179

Without a return switch, you save the code in one place:

 __block int seconds = 0; NSTimer* timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES usingBlock:^(NSTimer *timer) { seconds++; // Update UI if (seconds>=60*60*2) { [timer invalidate]; } }]; 
0
source

All Articles