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

发布于 2024-09-14 02:42:20 字数 579 浏览 5 评论 0 原文

有谁知道什么时候是停止 UIViewController 内部引用的 NSTimer 的最佳时间,以避免计时器和控制器之间的保留循环?

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

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

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

上面的代码导致计时器保存对视图控制器的引用。

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

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

我尝试将其放入 ViewDidUnload 中,但直到视图收到内存警告时才会触发,所以不是一个好地方。我尝试了dealloc,但是只要计时器还活着,dealloc就永远不会被调用(先有鸡还是先有蛋的问题)。

有什么好的建议吗?

Does any one know when is the best time to stop an NSTimer that is held reference inside of a UIViewController to avoid retain cycle between the timer and the controller?

Here is the question in more details: I have an NSTimer inside of a UIViewController.

During ViewDidLoad of the view controller, I start the timer:

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

The above causes the timer to hold a reference to the view controller.

Now I want to release my controller (parent controller releases it for example)

the question is: where can I put the call to [statusTimer invalidate] to force the timer to release the reference to the controller?

I tried putting it in ViewDidUnload, but that does not get fired until the view receives a memory warning, so not a good place. I tried dealloc, but dealloc will never get called as long as the timer is alive (chicken & egg problem).

Any good suggestions?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

拥醉 2024-09-21 02:42:20
  1. 您可以首先避免保留周期,例如,将计时器瞄准一个 StatusUpdate 对象,该对象保存对控制器的非保留(弱)引用,或者通过使用控制器的指针初始化的 StatusUpdater 来保存对此的弱引用,并为您设置计时器。

    • 当目标窗口为 nil(应该处理您提供的 -viewDidDisappear: 的反例)以及 -viewDidDisappear: 中的反例。这确实意味着您的视图正在返回到您的控制器;如果您愿意,您可以通过向控制器发送 -view:willMoveToWindow: 消息或发布通知来避免伸手抓住计时器。

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

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

除了第一个之外的每一个建议都是一种与保留周期共存并在适当的时间打破它的方法。第一个建议实际上避免了保留周期。

  1. You could avoid the retain cycle to begin with by, e.g., aiming the timer at a StatusUpdate object that holds a non-retained (weak) reference to your controller, or by having a StatusUpdater that is initialized with a pointer your controller, holds a weak reference to that, and sets up the timer for you.

    • You could have the view stop the timer in -willMoveToWindow: when the target window is nil (which should handle the counterexample to -viewDidDisappear: that you provided) as well as in -viewDidDisappear:. This does mean your view is reaching back into your controller; you could avoid reaching in to grab the timer by just send the controller a -view:willMoveToWindow: message or by posting a notification, if you care.

    • Presumably, you're the one causing the view to be removed from the window, so you could add a line to stop the timer alongside the line that evicts the view.

    • You could use a non-repeating timer. It will invalidate as soon as it fires. You can then test in the callback whether a new non-repeating timer should be created, and, if so, create it. The unwanted retain cycle will then only keep the timer and controller pair around till the next fire date. With a 1 second fire date, you wouldn't have much to worry about.

Every suggestion but the first is a way to live with the retain cycle and break it at the appropriate time. The first suggestion actually avoids the retain cycle.

指尖凝香 2024-09-21 02:42:20

解决这个问题的一种方法是让 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];

One way around it is to make the NStimer hold a weak reference to your UIViewController. I created a class that holds a weak reference to your object and forwards the calls to that:

#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

and you use it like this:

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

Your dealloc method gets called (unlike before) and inside it you just call:

[statusTimer invalidate];
放手` 2024-09-21 02:42:20

您可以尝试使用 - (void)viewDidDisappear:(BOOL)animated 然后您应该在 - (void)viewDidAppear:(BOOL)animated 中再次验证它

更多内容请参见此处

You can try with - (void)viewDidDisappear:(BOOL)animated and then you should validate it again in - (void)viewDidAppear:(BOOL)animated

More here

っ左 2024-09-21 02:42:20

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

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

For @available(iOS 10.0, *) you could also use:

Timer.scheduledTimer(
    withTimeInterval: 1,
    repeats: true,
    block: { [weak self] _ in
        self?.updateStatus()
    }
)
生来就爱笑 2024-09-21 02:42:20

-viewDidDisappear 方法可能就是您正在寻找的方法。每当视图被隐藏或关闭时就会调用它。

The -viewDidDisappear method may be what you're looking for. It's called whenever the view is hidden or dismissed.

冷月断魂刀 2024-09-21 02:42:20

使内部计时器无效 - (void)viewWillDisappear:(BOOL)animated 对我有用

invalidate timer inside - (void)viewWillDisappear:(BOOL)animated did work for me

夜雨飘雪 2024-09-21 02:42:20

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

目标在dealloc中调用[weakrefclear]和[timerinvalidate]等。恶心,不是吗?

(下一个明显的事情是编写您自己的计时器类来为您处理所有这些。)

I wrote a "weak reference" class for exactly this reason. It subclasses NSObject, but forwards all methods that NSObject doesn't support to a target object. The timer retains the weakref, but the weakref doesn't retain its target, so there's no retain cycle.

The target calls [weakref clear] and [timer invalidate] or so in dealloc. Icky, isn't it?

(The next obvious thing is to write your own timer class that handles all of this for you.)

葬心 2024-09-21 02:42:20

如果timer.REPEAT设置为YES,定时器的所有者(例如视图控制器或视图)将不会被释放,直到定时器失效。

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

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

  1. 当视图处于 启动计时器
  2. 添加到超级视图,当视图从超级视图中删除时

,停止计时器,因此我选择 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 拥有,也许 viewWillAppear:viewWillDisappear: 是启动和停止计时器的好地方。

If the timer.REPEAT is set to YES, the owner of the timer (e.g. view controller or view) will not be deallocated until the timer is invalidated.

The solution to this question is to find some trigger point to stop your timer.

For example, I start a timer to play animated GIF images in a view, and the trigger point would be:

  1. when the view is added to the superview, start the timer
  2. when the view is removed from the superview, stop the timer

so I choose the UIView's willMoveToWindow: method as such:

- (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;
    }
}

If your timer is owned by a ViewController, maybe viewWillAppear: and viewWillDisappear: are a good place for you to start and stop the timer.

情绪操控生活 2024-09-21 02:42:20

我遇到了完全相同的问题,最后我决定重写视图控制器的release方法来寻找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];
}

我更喜欢这种方法,因为它保持简单并封装在一个对象(即视图控制器)中,因此更易于调试。然而,我不喜欢乱搞保留/释放链,但我找不到解决这个问题的方法。

希望这会有所帮助,如果您确实找到了更好的方法,也很乐意听到。

戴夫

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

I had exactly the same issue and in the end I decided to override the release method of the View Controller to look for the special case of the retainCount being 2 and my timer running. If the timer wasn't running then this would have caused the release count to drop to zero and then call 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];
}

I prefer this approach because it keeps it simple and encapsulated within the one object, i.e., the View Controller and therefore easier to debug. I don't like, however, mucking about with the retain/release chain but I cannot find a way around this.

Hope this helps and if you do find a better approach would love to hear it too.

Dave

EDIT: Should have been -(oneway void)

故事灯 2024-09-21 02:42:20

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

例如。

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

这样statustimer的引用计数器就会自动减1
&分配的内存上的数据也将被删除,

您也可以在 - (void)viewDidDisappear:(BOOL)animated 函数中编写此代码

You can write this code in dealloc function of view controller

for eg.

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

this way the reference counter of statustimer will automatically decrement by 1
& also the data on the allocated memory will also erase

also you can write this code in - (void)viewDidDisappear:(BOOL)animated function

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文