Шаблон наблюдателя для секундомера - PullRequest
7 голосов
/ 22 ноября 2011

Я пытаюсь реализовать секундомер на основе модели MVC.

Секундомер использует NSTimer с селектором -(void) tick, который вызывается каждый раз.

Я пыталсясделать секундомер в качестве модели для повторного использования, но я столкнулся с некоторыми проблемами проектирования, касающимися обновления контроллера представления для каждого тика.

Сначала я создал протокол с методом тика и сделал контроллер представления его делегатом,Затем контроллер представления обновляет представления на основе свойств таймера на каждом тике.elapsedTime является только для чтения NSTimeInterval.

Это работает, но я думаю, что это может быть плохой дизайн.Я новичок в Objective-C / Cocoa Touch.Должен ли я использовать что-то вроде КВО?Или есть более элегантное решение для модели, чтобы уведомить контроллер представления, что elapsedTime изменился?

Ответы [ 3 ]

5 голосов
/ 22 ноября 2011

Таймер - это хороший способ периодически обновлять пользовательский интерфейс, но не использовать его для отслеживания времени. NSTimer может дрейфовать , и любые небольшие ошибки могут накапливаться, если вы используете таймер для накопления секунд.

Вместо этого используйте NSTimer, чтобы вызвать метод, который обновляет ваш пользовательский интерфейс, но получить в реальном времениNSDate.NSDate даст вам разрешение в миллисекундах;если вам действительно нужно лучше, рассмотрите это предложение использовать функции синхронизации Маха .Итак, используя 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 для измерения общего прошедшего времени.

Дополнительную полезную информацию вы найдете в этой теме SO .

2 голосов
/ 22 ноября 2011

Я бы порекомендовал против КВО для этой проблемы. Это вносит много сложности (и несколько раздражающих ошибок) для небольшой выгоды здесь. KVO важен в тех случаях, когда вам необходимо обеспечить абсолютно минимальные накладные расходы. Apple часто использует его для низкоуровневых высокопроизводительных объектов, таких как слои. Это единственное общедоступное решение, которое предлагает нулевые накладные расходы, когда нет наблюдателя. В большинстве случаев вам это не нужно. Правильная обработка KVO может быть сложной задачей, и ошибки, которые он может создать, раздражают, чтобы выследить.

В вашем делегатском подходе нет ничего плохого. Это правильно MVC. Единственное, о чем вам нужно беспокоиться, это то, что NSTimer не дает серьезных обещаний, когда его вызывают. В некоторых случаях повторяющийся таймер может даже пропустить. Чтобы избежать этой проблемы, вы обычно рассчитываете elapsedTime на основе текущего времени, а не увеличивая его. Если таймер может сделать паузу, вам нужно сохранить аккумулятор и дату «когда я в последний раз».

Если вам нужны более точные или более дешевые таймеры, вы можете посмотреть на dispatch_source_set_timer(), но для простого секундомера, ориентированного на человека, NSTimer подойдет и отличный выбор для простого проекта.

0 голосов
/ 22 ноября 2011

В последнее время я использовал блоки вместо простых старых @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];
                                                }




}];  
...