【问题标题】:Observer pattern for stopwatch秒表的观察者模式
【发布时间】:2012-01-03 08:20:29
【问题描述】:

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

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

我尝试将秒表作为可重用模型,但在如何更新每个刻度的视图控制器方面遇到了一些设计问题。

首先,我使用 tick 方法创建了一个协议,并将视图控制器作为其委托。然后,视图控制器在每个滴答时根据计时器的属性更新视图。 elapsedTime 是一个只读的 NSTimeInterval。

它有效,但我认为它可能是糟糕的设计。我是一名 Objective-C/Cocoa Touch 初学者。我应该使用 KVO 之类的东西吗?或者有没有更优雅的解决方案让模型通知视图控制器elapsedTime 发生了变化?

【问题讨论】:

  • 第一个问题很好!欢迎来到 SO!
  • 定时器和视图控制器到底是什么关系?计时器是否归 VC 所有?
  • 谢谢 :) Timer 归 VC 所有,是的。我已经实现了一个从 Timer 继承的 IntervalTimer,然后 VC 拥有 IntervalTimer - IntervalTimer 实际上给我带来了一些麻烦。

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


【解决方案1】:

计时器是确保您定期更新用户界面的好方法,但不要使用它来跟踪时间。 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 中找到更多有用信息。

【讨论】:

    【解决方案2】:

    对于这个问题,我建议不要使用 KVO。它引入了很多复杂性(和几个烦人的陷阱),在这里没有什么好处。在您需要确保绝对最小开销的情况下,KVO 很重要。 Apple 在低级别、高性能对象(如图层)的情况下大量使用它。它是唯一一个在没有观察者时提供零开销的普遍可用的解决方案。大多数时候,你不需要那个。正确处理 KVO 可能会很棘手,而且它可能产生的错误很难追踪。

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

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

    【讨论】:

    • 我知道跳过计时器和使用增量的问题。我正在使用 NSDate 并在 setElapsedTime 中比较 NSDates(+ 使用暂停时的偏移量)
    • 我正在尝试实现一个间隔倒计时计时器,它继承自秒表并具有一些额外的属性,包括 workInterval 和 restInterval,但我的 ViewController 中的标签更新有点奇怪。我认为这可能与 ViewController 的滴答声、计时器滴答声和 IntervalTimers 滴答之间的顺序调用有关。
    【解决方案3】:

    最近,我一直在使用块而不是普通的旧 @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];
                                                    }
    
    
    
    
    }];  
    

    【讨论】:

      猜你喜欢
      • 2016-02-20
      • 2023-04-10
      • 1970-01-01
      • 1970-01-01
      • 2013-02-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-22
      相关资源
      最近更新 更多