Проблемы с аннулированием и повторным созданием NSTimer(ов)

У меня проблемы с запуском и остановкой NSTimers. В документах говорится, что таймер остановлен [таймер недействителен];

У меня есть объект таймера, объявленный как таковой

.h
NSTimer *incrementTimer;
@property (nonatomic, retain) NSTimer *incrementTimer;
.m
@synthesize incrementTimer;
-(void)dealloc {
 [incrementTimer release];
 [super dealloc];
 }

-Обычно.

Когда это необходимо, мой метод делает следующее:

-(void)setGenCount {
    if(!condition1 && condition2) {
        incrementTimer = [NSTimer scheduledTimerWithTimeInterval: 2.0 
                                                      target: self 
                                                    selector:@selector(incrementBatteryVoltage:) 
                                                    userInfo: nil 
                                                     repeats: YES]; 
    }
}

Все выше работает нормально. Однако, как только этот таймер выполнит свою работу, я хочу, чтобы он аннулировал себя. Я аннулирую таймер, потому что есть метод равного уменьшения, который можно было бы вызвать и который будет бороться с incrementTimer, если он все еще активен. (Ранее я заметил, что два моих таймера, если они активны, действовали на один и тот же ivar, увеличивая и уменьшая значение (своего рода борьба)... без сбоев) Вызванный селектор работает следующим образом:

-(void)incrementBatteryVoltage:(NSTimer *)timer {
    if(battVoltage < 24.0) {
         generatorDisplay.battVoltage += 0.1;
      }
    if(battery1Voltage == 24.0) {
         [timer invalidate];
      }
  }

У меня есть равный метод, который уменьшает количество батарей. (упоминалось ранее)
Из-за особенностей моей программы: интерфейс имитирует отображение напряжения. Когда «машина» выключена, я хочу, чтобы все таймеры были признаны недействительными, независимо от того, какое значение напряжения. Я делаю это, проверяя, действителен ли таймер.

-(void)deEnergizeDisplays {

   if([decrementTimer isValid]) {
        [decrementTimer invalidate];
        decrementTimer = nil;
     }

    if([incrementTimer isValid]) {
       [incrementTimer invalidate];
       incrementTimer = nil;
    }

Я получаю многочисленные сбои "BAD_ACCESS". Ошибочный вызов линии всегда указывает на мой вызов [timer isValid]. Кажется, что если таймер недействителен... указатель тоже не существует. Я знаю, что сообщение [таймер недействителен] отключает таймер, затем он удаляется из цикла выполнения, а затем освобождается. И я так понимаю: это автоматически выпускаемый объект в соответствии с его соглашением об именах.

Моя мысль такова: если я отправляю сообщение об удержании, не должна ли ссылка все еще существовать? Я пробовал несколько комбинаций, забирая:

timer = nil;

или даже вместо:

if([timer isValid])

Я пытался :

if([timer != nil])

и:

if(timer)

Я всегда получаю один и тот же сбой. Спасибо за любую помощь в запуске и остановке NSTimers.


person samfu_1    schedule 02.02.2010    source источник
comment
Я думаю, что это не связано с вашим сбоем, но вы, вероятно, не хотите проверять числа с плавающей запятой на явное равенство.   -  person Carl Norum    schedule 03.02.2010
comment
Как бы вы выполнили эту задачу? Проверить наличие (battVoltage › 24)?   -  person samfu_1    schedule 03.02.2010
comment
Это лучший путь, да.   -  person Carl Norum    schedule 04.02.2010


Ответы (2)


ОБНОВЛЕНИЕ: См. Даррена ответ. Проблема в том, что вы не используете свой метод доступа к свойствам при установке таймеров. Вместо:

incrementTimer = [NSTimer ...

У вас должно быть:

self.incrementTimer = [NSTimer ...

Синтаксис self.propertyName = ... вызовет ваш метод доступа и, таким образом, автоматически сохранит объект, который вы ему отправляете (поскольку ваше свойство настроено как retain). Простой вызов propertyName = ... не использует метод доступа к свойству. Вы просто напрямую меняете значение своего ivar.


ОБНОВЛЕНИЕ № 2. После содержательного разговора с Питером Хози (см. комментарии) я удалил свое предыдущее предложение «никогда не сохранять и не освобождать» ваш объект таймера. Я также полностью переписал свой предыдущий код, потому что считаю, что следующий подход является лучшим:

Контроллер.h:

NSTimer *voltageTimer;
float targetBatteryVoltage;
...
@property (nonatomic, retain) NSTimer *voltageTimer;

Контроллер.m:

@implementation Controller
@synthesize voltageTimer;

- (void)stopVoltageTimer {
    [voltageTimer invalidate];
    self.voltageTimer = nil;
}

- (void)setTargetBatteryVoltage:(float)target {
    [voltageTimer invalidate];
    targetBatteryVoltage = target;
    self.voltageTimer = [NSTimer scheduledTimerWithTimeInterval: 2.0
                                target: self
                              selector: @selector(updateBatteryVoltage:)
                              userInfo: nil
                               repeats: YES];
}

- (void)updateBatteryVoltage:(NSTimer *)timer {
    const float increment = 0.1;
    if (abs(battVoltage - targetBatteryVoltage) < increment) {
        [timer invalidate];
    }
    else if (battVoltage < targetBatteryVoltage) {
        generatorDisplay.battVoltage += increment;
    }
    else if (battVoltage > targetBatteryVoltage) {
        generatorDisplay.battVoltage -= increment;
    }
}

Теперь вы можете просто установить целевое напряжение батареи, и волшебство таймера произойдет за кулисами:

[self setTargetBatteryVoltage:24.0];

Ваш метод отключения питания будет выглядеть следующим образом:

- (void)deEnergizeDisplays {
    [self stopVoltageTimer];
}
person e.James    schedule 02.02.2010
comment
ИДЕАЛЬНО. Спасибо за подробное объяснение и альтернативный способ решения проблемы! - person samfu_1; 03.02.2010
comment
«Никогда не удерживайте и не отпускайте таймер. Используйте только invalidate». Именно отсутствие сохранения этого вопроса привело к сбою вопрошающего при попытке отправить ему сообщение; объект таймера перестал существовать, когда обратный вызов таймера сделал его недействительным. Если объект владеет другим, он должен сохранить его; нет веской причины делать исключение для таймеров. - person Peter Hosey; 03.02.2010
comment
Что касается циклической ссылки (таймеры сохраняют/сильно ссылаются на свои цели), решите эту проблему, если все, что уничтожает этот объект, скажет ему сначала прекратить мониторинг напряжения батареи. В этом методе аннулируйте и отпустите таймер. Это откроет круг и позволит вам уничтожить объект монитора напряжения батареи. - person Peter Hosey; 03.02.2010
comment
@Peter Hosey: я понимаю вашу точку зрения, но я с уважением не согласен. На мой взгляд, таймер принадлежит runLoop, а не контроллеру, который его инициировал. Особенно в этом случае, когда единственная причина для таймера — запуск события через равные промежутки времени. - person e.James; 03.02.2010
comment
@Peter Hosey: документация Apple дает некоторые конкретные советы по управлению памятью для NSTimers: поскольку цикл выполнения поддерживает таймер, с точки зрения управления памятью обычно нет необходимости сохранять ссылку на таймер после того, как вы его запланировали. developer.apple.com/mac/library/documentation/cocoa/Conceptual/ - person e.James; 03.02.2010
comment
Да, я знаю, что написано в документации. Я не согласен с этим. Таймер принадлежит циклу выполнения и контроллеру, создавшему таймер. Я не вижу причин, по которым контроллер должен создавать таймер, знать о таймере и участвовать в его жизни, но не владеть им. - person Peter Hosey; 03.02.2010
comment
Я взял на себя смелость продолжить этот спор в своей голове, и вы будете счастливы услышать, что мой мозг объявил вас победителем :) Контроллер должен поддерживать ссылку на таймер, чтобы deEnergizeDisplays может отключить его, и это не может работать надежно, если контроллер не сохраняет таймер. Контроллеру нужно, чтобы объект таймера был доступен, даже если он был признан недействительным. Спасибо, что поставили меня на место. - person e.James; 03.02.2010
comment
Господа, я очень ценю поучительную дискуссию. e.James, ваш предыдущий комментарий ЯВЛЯЕТСЯ причиной, по которой я подумал, что мне нужно будет сохранить ссылку. Документация расплывчата, и мое понимание NSTimer носит аморфный характер. - person samfu_1; 03.02.2010
comment
@samfu_1: Я рад, что ты полагался на свои инстинкты, а не на мои! - person e.James; 03.02.2010
comment
e.James - спасибо, как всегда, за подробные ответы ... они действительно помогли мне разобраться с NSTimer и заставить его работать как надо (это мой первый раз и все такое) - person iwasrobbed; 23.05.2010
comment
Добро пожаловать, и я ценю, что вы нашли время, чтобы опубликовать благодарственное письмо. Всегда приятно слышать, что все это печатание того стоило ;) - person e.James; 23.05.2010
comment
Спасибо, Джеймс, я пытался неделю, но ваше предложение помогло. Большое спасибо, чувак :) - person Abdurrashid Khatri; 13.08.2012

Вам нужно retain значение, присвоенное incrementTimer в setGenCount. Вы можете сделать это автоматически, используя свое синтезированное свойство, доступ к которому осуществляется через self.:

self.incrementTimer = [NSTimer scheduledTimerWithTimeInterval: ...
person Darren    schedule 02.02.2010
comment
будет ли это сделано с помощью: - person samfu_1; 03.02.2010