释放NSTimer的正确方法是什么?

4
在我的dealloc方法中释放NSTimer的正确方法是什么?它是用以下代码创建的吗?
-(void)mainTimerLoop {

     mainTimer = [NSTimer scheduledTimerWithTimeInterval:1/10
                                                  target:self 
                                                selector:@selector(gameLoop) 
                                                userInfo:nil
                                                 repeats:YES];
}

谢谢

5个回答

16
你现在的做法,永远不会触发dealloc。定时器会保留它的目标对象,也就是说,定时器已经将你保留了下来。只有当定时器被无效化后,你才会被释放。由于是你创建了定时器,所以在dealloc之前,你必须将其无效化,因为定时器的保留会防止你的对象被dealloc

你有两个选择:

  • 找到另一个地方使定时器无效化(视图离开屏幕、应用程序终止等)
  • 将其他东西设置为定时器的目标对象。

以下是后者的示例:

@interface GameLoopTimerTarget : NSObject {
    id owner;  /* not retained! */
}
- (id)initWithOwner:(id)owner;
- (void)timerDidFire:(NSTimer *)t;
@end

@implementation GameLoopTimerTarget
- (id)initWithOwner:(id)owner_ {
    self = [super init];
    if (!self) return nil;

    owner = owner_;
    return self;
}

- (void)timerDidFire:(NSTimer *)t {
#pragma unused (t)
    [owner performSelector:@selector(gameLoop)];
}
@end

/* In your main object… */
/* assume synthesized:
@property (retain, NS_NONATOMIC_IPHONE_ONLY) GameLoopTimer *mainTimerTarget; */
- (void)mainTimerLoop {
    self.mainTimerTarget = [[[GameLoopTimerTarget alloc] initWithOwner:self] autorelease];
    mainTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/10.0 target:self.mainTimerTarget selector:@selector(timerDidFire:) userInfo:nil repeats:YES];
}

- (void)dealloc {
    /* other stuff */
    [timer invalidate], timer = nil;
    [mainTimerTarget release], mainTimerTarget = nil;
    /* more stuff */
    [super dealloc];
}

请注意时间间隔是1.0/10.0,这也可以写成0.1,但不能写成1/10,因为该除法将截断为0.0
另外,请注意这样可以打破保留循环:
  • 你和你的定时器都会保留定时器目标。
  • 你在正常时间到达后释放。
  • 然后你使定时器无效并释放定时器目标。
  • 然后定时器目标被解除分配。

2

一个有效的NSTimer会被run loop保留,如果它是重复的,它将永远存在或直到你使其失效。你不应该释放它,因为在你的示例代码中,你没有明确地保留它。如果你使它无效,它将不再被run loop保留,并且将被自动释放。

对于重复计时器可能没问题,但对于一次性计时器来说是危险的,因为它可能在你检查它是否有效和/或尝试使其失效之前就被释放了(这会导致应用程序崩溃)。因此,如果你计划以任何方式查看计时器变量的创建后(包括检查、使其失效和/或释放),在你的应用程序中显式地保留它可能是一个好的做法,然后在它失效并完成任务后释放它并将其设置为nil。

如果你将其声明为保留属性,你可以在一个语句中释放它并将其设置为nil。然后你可以写:

self.timer = nil;

你确定在Objective-C中将对象设置为nil会调用它的释放方法吗? - keith
如果您的目标指定了自动保留计数(ARC),这是过去十年甚至更长时间的默认设置。 - Motti Shneor

0

你在这里关于NSTimer的回答非常好 如何使用NSTimer?,他们谈到了如何停止重复的NSTimer。

[myTimer invalidate];

15
这是错误的,而且这是非常糟糕的建议!使用NSTimer进行内存管理是非常奇怪的:它会保留目标对象,并且除非计时器被使无效,否则不会释放目标对象。如果他以self为目标创建一个重复的NSTimer,计时器会保留self,因此dealloc方法直到计时器被使无效后才会被调用。因此不要说“在dealloc方法中停止计时器”。诀窍是找到一个在dealloc方法之前停止计时器的地方。 - matt
因为matt给出的原因,我也被down-voted了。需要在dealloc之外使计时器失效并释放它。 - theLastNightTrain
但是这个答案在dealloc中没有嵌入无效化,是谁给你的这个想法? - Motti Shneor

0

我认为这里最好的建议是 -

不要保留 NSTimer 实例,也不要释放它。

一旦它被安排在一个 NSRunloop 上(在 OP 的示例中是当前 runloop),NSTimer 就会被 runloop 保留,直到被无效化或者 runloop 停止。

你应该在正确的时间和同一个线程上无效化你的计时器。

还要记住,NSTimer 会保留它的目标,并且在它自己死亡之前不会让目标“死亡”。设计你的代码,使你不会有一个保留循环,防止释放你的对象(持有计时器)和计时器(持有你的对象)。


-3

你不需要释放它,因为它将被自动释放。由方便方法创建的任何内容(即您不会自己调用alloc)都是被调用函数负责内存管理的,这通常意味着它将在返回对象之前调用autorelease

我会使用retain关键字将计时器分配给属性,以确保它不会被解除分配。通常,如果自动释放的对象没有任何保留,则会在事件循环中将其释放。


1
这是错误的。计时器由其所在的NSRunLoop保留。只有在计时器被无效化之后,它才会被释放。 - matt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接