NSTimer内存管理

14

当我执行这段代码时:

[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(showButtons) userInfo:nil repeats:NO];

我需要将其设为nil或释放它,或者做其他内存管理吗?

我正在使用ARC。

4个回答

37

是的,NSTimer会对target保持强引用,这可能会导致强引用循环(即保留循环),尤其是在重复的计时器中。但在您的示例中,计时器不会重复,并且仅延迟0.5秒,因此最坏的情况是在0.5秒后自动解决强引用循环。

但一个常见的未解决的强引用循环的例子是,有一个重复的NSTimer属性的UIViewController,因为NSTimerUIViewController保持强引用,所以控制器将被保留。

因此,如果您将NSTimer作为实例变量保留,则应该使用invalidate来解决强引用循环。如果您只调用了scheduledTimerWithTimeInterval,但没有将其保存到实例变量中(如您的示例所示),则当NSTimer完成时,您的强引用循环将被解决。

顺便提一下,如果你正在处理重复的NSTimers,不要试图在NSTimer所有者的dealloc中使它们失效,因为显然直到强引用循环被解决之前dealloc不会被调用。例如,在UIViewController的情况下,你可以在viewDidDisappear中这样做。

顺便说一下,高级内存管理编程指南解释了什么是强引用循环。显然,这是在描述弱引用的正确使用的部分,这里并不适用(因为你无法控制NSTimer对目标使用强引用),但它确实很好地解释了强引用循环的概念。


如果你不想让你的NSTimerself保持强引用,在macOS 10.12和iOS 10或更高版本中,你可以使用块版本,然后使用weakSelf模式:

typeof(self) __weak weakSelf = self;
[NSTimer scheduledTimerWithTimeInterval:0.5 repeats:false block:^(NSTimer * _Nonnull timer) {    [weakSelf showButtons];
}];

顺便提一下,我注意到你正在调用showButtons。如果你只是想在视图上显示一些控件,你可以完全不使用NSTimer,而是像这样做:
self.button1.alpha = 0.0;
self.button2.alpha = 0.0;

[UIView animateWithDuration:0.25
                      delay:0.5
                    options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction
                 animations:^{
                     self.button1.alpha = 1.0;
                     self.button2.alpha = 1.0;
                 }
                 completion:nil];

这不会受到NSTimer对象的保留问题,而且可以在一个语句中执行延迟和优雅显示按钮。如果您在showButtons方法中进行其他处理,可以将其放在完成块中。

很好的答案,但也许我没有清楚地表明我实际上并没有在.h文件中声明NSTimer* timer,我只是在.m文件中添加了那段代码,所以我无法使其失效或置空。在这种情况下,如果我使用我的计时器,还是最好使用您提供给我的代码? - Alessandro
@Alessandro 我其实以为你没有为计时器维护一个 ivar。但是,显然,如果你想在 viewWillDisappearinvalidate,那么你必须这样做。(顺便说一下,你不必将私有 ivars 放在 .h 文件中;私有类扩展更好。)还有几次你提到设置 NSTimernil。请注意,这并不能解决强引用循环的问题。计时器要么需要完成(而不是重复),要么你需要使用新的 ivar 进行 invalidate - Rob
我完全理解需要使计时器无效才能将其从运行循环中移除。在这样做之后,重新分配该属性方面,是否需要将计时器设置为nil并执行if timer!= nil或者只需执行!timer.isValid即可?换句话说,是否有必要执行timer = nil?在您之前的评论中,您已经说过这与内存管理无关,因此我想知道为什么许多开发人员仍然这样做,而isValid可以增加清晰度... - mfaani

2
如果你将它保存在一个属性中,那么在它触发选择器后,确实需要将其设置为nil。
如果你的类因任何原因被释放,保存它也是安全的,这样你就可以在需要时[timer invalidate]

如果你将该属性设置为weak,你就不必自己将其设为nil。在它触发后(假设它是非重复的),它会自动释放。 - Rob
@Rob 请问这样说是否正确:如果是重复的,则调用invalidate变得必要。否则,运行循环将保留其指针指向弱引用计时器的视图控制器。 - mfaani
或者更准确地说,运行循环将保持对重复计时器的强引用。如果计时器弱引用视图控制器,则内存影响将很小,但不再需要的计时器将继续触发。 - Rob

1

是的,您可以使用:myTimer=[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(showButtons) userInfo:nil repeats:NO]; 然后在您的viewDidDisappear中使用[myTimer invalidate]


1
使用使用闭包的新Timer方法。
timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: {[weak self] (timer) in
   print("Tick tock")
   guard let ws = self else { return }
   //some action that repeats
   ws.myViewControllerMethod()
})

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