在UIViewController中使NSTimer无效以避免保留周期的最佳时机

时间:2010-08-13 15:28:54

标签: iphone objective-c memory-management memory-leaks nstimer

有没有人知道何时是停止在UIViewController内部保持引用的NSTimer的最佳时间,以避免在定时器和控制器之间保留周期?

以下是更详细的问题:我在UIViewController中有一个NSTimer。

在视图控制器的ViewDidLoad中,我启动计时器:

statusTimer = [NSTimer scheduledTimerWithTimeInterval: 1 target: self selector: @selector(updateStatus) userInfo: nil repeats: YES];

以上导致计时器保持对视图控制器的引用。

现在我想释放我的控制器(例如,父控制器释放它)

问题是:我在哪里可以调用[statusTimer invalidate]来强制定时器释放对控制器的引用?

我尝试将它放在ViewDidUnload中,但是在视图收到内存警告之前不会被触发,因此不是一个好地方。我尝试了dealloc,但只要计时器还活着(鸡和鸡蛋问题),dealloc就永远不会被调用。

有什么好的建议吗?

10 个答案:

答案 0 :(得分:21)

  1. 您可以避免保留周期开始,例如,将计时器瞄准一个StatusUpdate对象,该对象保存对控制器的非保留(弱)引用,或者通过一个用你的控制器指针初始化的StatusUpdater,保持对它的弱引用,并为你设置计时器。

    • 当目标窗口为-willMoveToWindow:时,您可以让视图在nil 中停止计时器(应该处理-viewDidDisappear:的反例你提供的)以及-viewDidDisappear:。这意味着您的视图将重新回到您的控制器中;如果您愿意,只需向控制器发送-view:willMoveToWindow:消息或发布通知,就可以避免进入以获取计时器。

    • 据推测,您是导致视图从窗口中移除的那个,因此您可以添加一行来停止计时器旁边的那个驱逐视图的行。

    • 你可以使用非重复计时器。一旦它触发它就会失效。然后,您可以在回调中测试是否应创建新的非重复计时器,如果是,则创建它。然后,不需要的保留周期将仅保持定时器和控制器对,直到下一个发火日期。只需1秒的开火日期,您就不用担心了。

  2. 每个建议,但第一个是与保留周期一起生活并在适当的时间打破它的方法。第一个建议实际上避免了保留周期。

答案 1 :(得分:6)

一种方法是让NStimer保持对UIViewController的弱引用。我创建了一个类,它包含对象的弱引用,并将调用转发到:

#import <Foundation/Foundation.h>

@interface WeakRefClass : NSObject

+ (id) getWeakReferenceOf: (id) source;

- (void)forwardInvocation:(NSInvocation *)anInvocation;

@property(nonatomic,assign) id source;

@end

@implementation WeakRefClass

@synthesize source;

- (id)init{
    self = [super init];
//    if (self) {
//    }
    return self;
}

+ (id) getWeakReferenceOf: (id) _source{

    WeakRefClass* ref = [[WeakRefClass alloc]init];
    ref.source = _source; //hold weak reference to original class

    return [ref autorelease];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    return [[self.source class ] instanceMethodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    [anInvocation    invokeWithTarget:self.source ];

}

@end

你可以这样使用它:

statusTimer = [NSTimer scheduledTimerWithTimeInterval: 1 target: [WeakRefClass getWeakReferenceOf:self] selector: @selector(updateStatus) userInfo: nil repeats: YES];

你的dealloc方法被调用(与之前不同),在其中你只需要调用:

[statusTimer invalidate];

答案 2 :(得分:3)

您可以尝试使用- (void)viewDidDisappear:(BOOL)animated,然后再次在- (void)viewDidAppear:(BOOL)animated

中对其进行验证

More here

答案 3 :(得分:1)

-viewDidDisappear方法可能就是您要找的方法。只要隐藏或解除视图,就会调用它。

答案 4 :(得分:1)

使计时器无效 - (void)viewWillDisappear:(BOOL)动画确实对我有用

答案 5 :(得分:0)

出于这个原因,我写了一个“弱参考”类。它是NSObject的子类,但是将NSObject不支持的所有方法转发给目标对象。计时器保留弱参数,但弱参数不会保留其目标,因此没有保留周期。

目标在dealloc中调用[weakref clear]和[timer invalidate]左右。 Icky,不是吗?

(下一个显而易见的事情是编写自己的计时器类,为你处理所有这些。)

答案 6 :(得分:0)

如果timer.REPEAT设置为YES,则在计时器失效之前,不会释放计时器的所有者(例如视图控制器或视图)。

此问题的解决方案是找到一些触发点来停止计时器。

例如,我启动计时器在视图中播放动画GIF图像,触发点为:

  1. 将视图添加到superview时,启动计时器
  2. 从超视图中删除视图时,请停止计时器
  3. 所以我选择了UIView的{​​{1}}方法:

    willMoveToWindow:

    如果您的计时器由ViewController拥有,则- (void)willMoveToWindow:(UIWindow *)newWindow { if (self.animatedImages && newWindow) { _animationTimer = [NSTimer scheduledTimerWithTimeInterval:_animationInterval target:self selector:@selector(drawAnimationImages) userInfo:nil repeats:YES]; } else { [_animationTimer invalidate]; _animationTimer = nil; } } viewWillAppear:可能是您启动和停止计时器的好地方。

答案 7 :(得分:0)

对于 @available(iOS 10.0,*),您还可以使用:

Timer.scheduledTimer(
    withTimeInterval: 1,
    repeats: true,
    block: { [weak self] _ in
        self?.updateStatus()
    }
)

答案 8 :(得分:-1)

我有完全相同的问题,最后我决定覆盖View Controller的释放方法,以查找retainCount为2且我的计时器正在运行的特殊情况。如果计时器没有运行,那么这将导致释放计数降至零,然后调用dealloc。

- (oneway void) release {
    // Check for special case where the only retain is from the timer
    if (bTimerRunning && [self retainCount] == 2) {
        bTimerRunning = NO;
        [gameLoopTimer invalidate];
    }
    [super release];
}

我更喜欢这种方法,因为它保持简单并封装在一个对象中,即视图控制器,因此更容易调试。但是,我不喜欢使用保留/释放链,但我无法找到解决方法。

希望这会有所帮助,如果你找到更好的方法,也会喜欢听到它。

戴夫

编辑:应该是 - (单向无效)

答案 9 :(得分:-3)

您可以在视图控制器的dealloc函数中编写此代码

例如。

-(void)dealloc
{
   if([statusTimer isValid])
  {
       [statusTimer inValidate];
       [statustimer release];
      statusTimer = nil;
  }
}

这样,statustimer的引用计数器将自动递减1 &安培;此外,分配的内存上的数据也将被删除

您也可以在- (void)viewDidDisappear:(BOOL)animated函数

中编写此代码