【问题标题】:Problems invalidating & re-creating NSTimer(s)无效和重新创建 NSTimer(s) 的问题
【发布时间】:2010-02-02 22:04:25
【问题描述】:

我在启动和停止 NSTimers 时遇到问题。文档说计时器被 [timer invalidate] 停止;

我有一个这样声明的计时器对象

.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 invalidate] 消息会禁用计时器,然后将其从运行循环中删除,然后将其释放。我的理解是:根据命名约定,它是一个自动释放的对象。

我的想法是:如果我要发送保留消息,引用不应该仍然存在吗?我尝试了几种组合,带走:

timer = nil;

甚至代替:

if([timer isValid])

我试过了:

if([timer != nil])

和:

if(timer)

我总是遇到同样的崩溃。感谢您在启动和停止 NSTimers 方面的任何帮助。

【问题讨论】:

  • 我认为与您的崩溃无关,但您可能不想检查浮点数是否明确相等。
  • 你将如何完成这项任务?检查 (battVoltage > 24)?
  • 这是一个更好的方法,是的。

标签: objective-c cocoa nstimer


【解决方案1】:

更新:Darren's answer。问题是您在设置计时器时没有使用您的属性访问器。而不是:

incrementTimer = [NSTimer ...

你应该有:

self.incrementTimer = [NSTimer ...

self.propertyName = ... 语法将调用您的访问器方法,从而自动保留您发送给它的对象(因为您的属性设置为retain)。简单地调用propertyName = ... 使用属性访问器。您只是直接更改 ivar 的值。


更新 #2: 在与 Peter Hosey(参见 cmets)进行了一次富有启发性的对话后,我删除了我之前的建议,即“永远不要保留或释放”您的计时器对象。我还完全重写了我之前的代码,因为我认为以下是更好的方法:

Controller.h

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

Controller.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];
}

【讨论】:

  • “永远不要保留或释放计时器。仅使用invalidate。”不保留它正是发问者在尝试向其发送消息时崩溃的原因;当计时器回调使其无效时,计时器对象不再存在。如果一个对象拥有另一个对象,它应该保留它;没有充分的理由对计时器进行例外处理。
  • 至于循环引用(计时器保留/强引用其目标),通过让破坏该对象的任何东西告诉它首先停止监视电池电压来解决这个问题。在该方法中,使计时器无效并放开。这将打开圆圈并允许您破坏电池电压监控器对象。
  • @Peter Hosey:Apple 的文档为 NSTimers 的内存管理提供了一些具体建议:“因为运行循环维护计时器,从内存管理的角度来看,通常不需要保留对计时器的引用在你安排好之后。” developer.apple.com/mac/library/documentation/cocoa/Conceptual/…
  • 是的,我知道文档是怎么说的。我不同意。计时器归运行循环创建计时器的控制器所有。我看不出为什么控制器应该创建计时器、了解计时器并参与计时器的生命周期,但不拥有它。
  • 我冒昧地在脑海中继续这个论点,你会很高兴听到我的大脑已经宣布你是赢家 :) 控制器必须维护对计时器的引用,以便deEnergizeDisplays 可以将其关闭,除非控制器保留计时器,否则它无法可靠地工作。控制器需要可以访问计时器对象,即使它已失效。谢谢你让我直截了当。
【解决方案2】:

您需要将retain 分配给incrementTimer 的值在setGenCount 中。您可以使用合成属性自动执行此操作,该属性可通过self. 访问:

self.incrementTimer = [NSTimer scheduledTimerWithTimeInterval: ...

【讨论】:

  • 是否可以这样做:timer = [[NSTimer scheduledTimerWithTimeInterval: 2.0 target: self selector:@selector(incrementBatteryVoltage:) userInfo: nil repeats: YES] retain];
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-07
相关资源
最近更新 更多