有没有人知道何时是停止在UIViewController内部保持引用的NSTimer的最佳时间,以避免在定时器和控制器之间保留周期?
以下是更详细的问题:我在UIViewController中有一个NSTimer。
在视图控制器的ViewDidLoad中,我启动计时器:
statusTimer = [NSTimer scheduledTimerWithTimeInterval: 1 target: self selector: @selector(updateStatus) userInfo: nil repeats: YES];
以上导致计时器保持对视图控制器的引用。
现在我想释放我的控制器(例如,父控制器释放它)
问题是:我在哪里可以调用[statusTimer invalidate]来强制定时器释放对控制器的引用?
我尝试将它放在ViewDidUnload中,但是在视图收到内存警告之前不会被触发,因此不是一个好地方。我尝试了dealloc,但只要计时器还活着(鸡和鸡蛋问题),dealloc就永远不会被调用。
有什么好的建议吗?
答案 0 :(得分:21)
您可以避免保留周期开始,例如,将计时器瞄准一个StatusUpdate
对象,该对象保存对控制器的非保留(弱)引用,或者通过一个用你的控制器指针初始化的StatusUpdater
,保持对它的弱引用,并为你设置计时器。
当目标窗口为-willMoveToWindow:
时,您可以让视图在nil
中停止计时器(应该处理-viewDidDisappear:
的反例你提供的)以及-viewDidDisappear:
。这意味着您的视图将重新回到您的控制器中;如果您愿意,只需向控制器发送-view:willMoveToWindow:
消息或发布通知,就可以避免进入以获取计时器。
据推测,您是导致视图从窗口中移除的那个,因此您可以添加一行来停止计时器旁边的那个驱逐视图的行。
你可以使用非重复计时器。一旦它触发它就会失效。然后,您可以在回调中测试是否应创建新的非重复计时器,如果是,则创建它。然后,不需要的保留周期将仅保持定时器和控制器对,直到下一个发火日期。只需1秒的开火日期,您就不用担心了。
每个建议,但第一个是与保留周期一起生活并在适当的时间打破它的方法。第一个建议实际上避免了保留周期。
答案 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
答案 3 :(得分:1)
-viewDidDisappear方法可能就是您要找的方法。只要隐藏或解除视图,就会调用它。
答案 4 :(得分:1)
使计时器无效 - (void)viewWillDisappear:(BOOL)动画确实对我有用
答案 5 :(得分:0)
出于这个原因,我写了一个“弱参考”类。它是NSObject的子类,但是将NSObject不支持的所有方法转发给目标对象。计时器保留弱参数,但弱参数不会保留其目标,因此没有保留周期。
目标在dealloc中调用[weakref clear]和[timer invalidate]左右。 Icky,不是吗?
(下一个显而易见的事情是编写自己的计时器类,为你处理所有这些。)
答案 6 :(得分:0)
如果timer.REPEAT设置为YES
,则在计时器失效之前,不会释放计时器的所有者(例如视图控制器或视图)。
此问题的解决方案是找到一些触发点来停止计时器。
例如,我启动计时器在视图中播放动画GIF图像,触发点为:
所以我选择了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
函数