秒表的观察者模式

时间:2011-11-21 21:04:28

标签: objective-c cocoa-touch model-view-controller nstimer

我正在尝试基于MVC模型实现秒表。

秒表使用NSTimer,每次超时都会调用选择器-(void) tick

我试图将秒表作为可重用性的模型,但是我遇到了一些关于如何为每个滴答更新视图控制器的设计问题。

首先,我使用tick方法创建了一个协议,并使视图控制器成为委托。然后,视图控制器根据每个刻度线上的计时器属性更新视图。 elapsedTime是一个只读的NSTimeInterval。

它有效,但我认为这可能是糟糕的设计。我是Objective-C / Cocoa Touch初学者。我应该使用像KVO这样的东西吗?或者是否有更优雅的解决方案让模型通知视图控制器elapsedTime已更改?

3 个答案:

答案 0 :(得分:5)

计时器是确保定期更新用户界面但不使用它来跟踪时间的好方法。 NSTimer can drift,如果使用计时器累积秒数,任何小错误都会累积。

相反,使用NSTimer触发更新UI的方法,但使用NSDate获取实时。 NSDate将为您提供毫秒级的分辨率;如果你真的需要更好,请考虑this suggestion to use Mach's timing functions。因此,使用NSDate,您的代码可能是这样的:

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

如果您允许重新启动等,您的代码可能会更复杂一些,但重要的是您没有使用NSTimer来测量总耗用时间。

您可以在this SO thread中找到其他有用的信息。

答案 1 :(得分:2)

对于这个问题,我建议反对KVO。它引入了很多复杂性(以及几个恼人的陷阱),这里没什么好处。在需要确保绝对最小开销的情况下,KVO非常重要。对于像图层这样的低级高性能对象,Apple会大量使用它。它是唯一通用的解决方案,在没有观察者时提供零开销。大多数时候,你不需要那样做。正确处理KVO可能会非常棘手,而且它可以创建的错误很容易被追踪。

您的委托方法没有任何问题。这是正确的MVC。你真正需要担心的唯一一件事就是NSTimer没有对它何时被召唤做出强有力的承诺。在某些情况下甚至允许重复计时器跳过。为了避免这个问题,您通常希望根据当前时间而不是通过递增来计算elapsedTime。如果计时器可以暂停,那么你需要保留一个累加器和“我上次开始的时间”日期。

如果您需要更高精度或更低成本的计时器,您可以查看dispatch_source_set_timer(),但对于一个简单的以人为目标的秒表,NSTimer很好,是一个简单项目的绝佳选择

答案 2 :(得分:0)

最近,我一直在使用块而不是普通的旧@selector。它创建更好的代码并将逻辑保持在同一位置。

NSTimer中没有本机块支持,但我使用了来自https://gist.github.com/250662/d4f99aa9bde841107622c5a239e0fc6fa37cb179的类别

如果没有返回选择器,则将代码保存在一个位置:

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

                                                seconds++;
                                                // Update UI

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




}];