Iphone 在UIViewController内使NSTimer无效以避免保留周期的最佳时间

Iphone 在UIViewController内使NSTimer无效以避免保留周期的最佳时间,iphone,objective-c,memory-management,memory-leaks,nstimer,Iphone,Objective C,Memory Management,Memory Leaks,Nstimer,是否有人知道何时是停止UIViewController内的NSTimer的最佳时间,以避免保留计时器和控制器之间的循环 这里有一个更详细的问题:我在UIViewController中有一个NSTimer 在视图控制器的VIEWDID加载期间,我启动计时器: statusTimer = [NSTimer scheduledTimerWithTimeInterval: 1 target: self selector: @selector(updateStatus) userInfo: nil rep

是否有人知道何时是停止UIViewController内的NSTimer的最佳时间,以避免保留计时器和控制器之间的循环

这里有一个更详细的问题:我在UIViewController中有一个NSTimer

在视图控制器的VIEWDID加载期间,我启动计时器:

statusTimer = [NSTimer scheduledTimerWithTimeInterval: 1 target: self selector: @selector(updateStatus) userInfo: nil repeats: YES];
上述操作会导致计时器保留对视图控制器的引用

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

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

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

有什么好的建议吗?

这个方法可能正是你想要的。每当视图被隐藏或取消时,都会调用它。

您可以尝试使用
-(void)viewdide消失:(BOOL)animated
,然后应该在
-(void)viewdide出现:(BOOL)animated
中再次验证它

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

目标在dealoc中调用[weakref clear]和[timer invalidate]左右。讨厌,不是吗

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

  • 例如,您可以通过将计时器对准持有对控制器的非保留(弱)引用的
    StatusUpdate
    对象,或者通过使用控制器的指针初始化的
    StatusUpdater
    来避免保留循环,持有对该对象的弱引用,并为您设置计时器

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

    • 可能是您导致视图从窗口中删除,因此您可以在逐出视图的行旁边添加一行来停止计时器

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


  • 除了第一条建议外,每一条建议都是一种适应保留周期并在适当的时候打破它的方法。第一个建议实际上避免了保留周期。

    您可以在view controller的dealloc函数中编写此代码

    例如

    -(void)dealloc
    {
       if([statusTimer isValid])
      {
           [statusTimer inValidate];
           [statustimer release];
          statusTimer = nil;
      }
    }
    
    这样,statustimer的参考计数器将自动递减1 &此外,分配内存上的数据也将被擦除


    您也可以在
    -(void)viewdiddemouse:(BOOL)animated
    函数中编写此代码。我遇到了完全相同的问题,最后我决定重写视图控制器的释放方法,以查找重新计数为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];
    }
    
    我更喜欢这种方法,因为它使它保持简单并封装在一个对象中,即视图控制器中,因此更易于调试。然而,我不喜欢在保留/释放链上胡闹,但我找不到解决这个问题的方法

    希望这对你有所帮助,如果你真的找到了更好的方法,我也很乐意听到

    戴夫


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

    一种解决方法是使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
    
    您的dealloc方法将被调用(与以前不同),您只需在其中调用:

    [statusTimer invalidate];
    

    如果timer.REPEAT设置为
    YES
    ,则计时器的所有者(例如视图控制器或视图)将不会解除分配,直到计时器失效

    这个问题的解决办法是找到一些触发点来停止计时器。

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

  • 将视图添加到superview后,启动计时器
  • 从superview中删除视图后,停止计时器
  • 因此,我选择了
    UIView
    willMoveToWindow:
    方法:

    - (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;
        }
    }
    
    如果您的计时器为ViewController所有,则可能会出现
    视图:
    视图将消失:
    是启动和停止计时器的好地方。

    在内部使计时器无效-(void)viewwilldefine:(BOOL)动画确实对我有用适用于@可用(iOS 10.0,*)您还可以使用:

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

    但是,当我使用:[self.view removeFromSuperview]隐藏父视图时,从未调用ViewDidEvale。还有其他建议吗?此外,当在UINavigationController中时,不会调用ViewDidEnglish,因此我正在寻找一个更一般的位置来放置它。如果可以依赖始终调用ViewDidEnglish,ViewDidEnglish将是完美的,不幸的是,在UINavigationController中,ViewDidEnglish不是这种情况
    Timer.scheduledTimer(
        withTimeInterval: 1,
        repeats: true,
        block: { [weak self] _ in
            self?.updateStatus()
        }
    )