【问题标题】:How to know when to invalidate an `NSTimer`如何知道何时使“NSTimer”无效
【发布时间】:2013-01-23 21:59:11
【问题描述】:

这是我的问题:

我有一个模型类,其中有一个NSTimer,我希望计时器在模型对象的整个生命周期内运行。初始化很简单:我在init 方法中只有以下代码行:

self.maintainConnectionTimer = 
             [NSTimer scheduledTimerWithTimeInterval:1 
                                              target:self 
                                            selector:@selector(maintainConnection) 
                                            userInfo:nil 
                                             repeats:YES];

但是,我的问题是,如何在模型从内存中释放时使此计时器无效? 现在,这通常很容易,但是据我所知,当您安排NSTimer 操作系统维护一个指向 Timer 对象的强指针。

我应该如何处理这个问题?有没有在模型从内存中释放之前被调用的方法?

【问题讨论】:

  • 我以前从来没有真正使用过这个......当我学习Objective-C时,我总是被告知dealloc已经很少使用了。我的属性在dealloc 方法中仍然有效吗?
  • dealloc 怎么样?是的,他们会的。我正在输入它作为答案。
  • 酷!如果您将此作为答案发布,我会接受它
  • 查看其他人的评论。您将不得不采用不同的策略进行精简。
  • 当您完成对象时,您可能必须手动使计时器无效。只要定时器在运行,它就不会被释放。

标签: iphone ios objective-c automatic-ref-counting nstimer


【解决方案1】:

[NSTimer scheduledTimerWithTimeInterval:...]保留目标,所以如果目标是self,那么你的模型类实例永远不会被释放。

作为一种解决方法,可以使用单独的对象(在以下示例中称为 TimerTarget)。 TimerTarget 有一个对ModelClass引用,以避免保留循环。

这个“帮助类”看起来像这样。它的唯一目的是将定时器事件转发给“真正的目标”。

@interface TimerTarget : NSObject
@property(weak, nonatomic) id realTarget;
@end

@implementation TimerTarget

- (void)timerFired:(NSTimer*)theTimer
{
    [self.realTarget performSelector:@selector(timerFired:) withObject:theTimer];
}

@end

现在,在您的模型类中,您可以在dealloc 中创建一个计时器并使其无效:

@interface ModelClass ()
@property(strong, nonatomic) NSTimer *timer;
@end

@implementation ModelClass

- (id)init
{
    self = [super init];
    if (self) {
        TimerTarget *timerTarget = [[TimerTarget alloc] init];
        timerTarget.realTarget = self;
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1
                                                 target:timerTarget
                                               selector:@selector(timerFired:)
                                               userInfo:nil repeats:YES];
    }
    return self;
}

- (void)dealloc
{
    [self.timer invalidate]; // This releases the TimerTarget as well!
    NSLog(@"ModelClass dealloc");
}

- (void)timerFired:(NSTimer*)theTimer
{
    NSLog(@"Timer fired");
}

@end

所以我们有

modelInstance ===> timer ===> timerTarget ---> modelInstance
(===> : strong reference, ---> : weak reference)

请注意,不再有从计时器到模型类实例的(强)引用。

我已使用以下代码对此进行了测试,该代码创建了一个 ModelClass 实例并在 5 秒后释放它:

__block ModelClass *modelInstance = [[ModelClass alloc] init];
int64_t delayInSeconds = 5.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    modelInstance = nil;
});

输出:

2013-01-23 23:54:11.483 timertest[16576:c07] Timer fired
2013-01-23 23:54:12.483 timertest[16576:c07] Timer fired
2013-01-23 23:54:13.483 timertest[16576:c07] Timer fired
2013-01-23 23:54:14.483 timertest[16576:c07] Timer fired
2013-01-23 23:54:15.483 timertest[16576:c07] Timer fired
2013-01-23 23:54:15.484 timertest[16576:c07] ModelClass dealloc

【讨论】:

  • +1,由于没有其他直接的方法可以做到这一点,我认为在这种情况下这将是更好的选择。
  • 可以将其封装在自定义的AutoreleasingTimer 类中。
  • 根据 Apple 文档,我们“不应该尝试继承 NSTimer”。也许这样的 AutoreleasingTimer 类可以具有返回 NSTimer 对象的工厂方法,其目标设置为内部 AutoreleasingTimerTarget 类?
  • @DavidSchwartz:你说得对,继承 NSTimer 并不是我的意思。
  • 啊,对了 - 重读您的评论后,我意识到您从未建议继承 NSTimer。我很抱歉。
【解决方案2】:

基于@Martin R 的想法,我创建了更易于使用的自定义类,并添加了一些检查以避免崩溃。

@interface EATimerTarget : NSObject

// Initialize with block to avoid missing call back
- (instancetype)initWithRealTarget:(id)realTarget timerBlock:(void(^)(NSTimer *))block;

// For NSTimer @selector() parameter
- (void)timerFired:(NSTimer *)timer;

@end

@interface EATimerTarget ()
@property (weak, nonatomic) id realTarget;
@property (nonatomic, copy) void (^timerBlock)(NSTimer *); // use 'copy' to avoid retain counting
@end

@implementation EATimerTarget

- (instancetype)initWithRealTarget:(id)realTarget timerBlock:(void (^)(NSTimer *))block {
    self = [super init];
    if (self) {
        self.realTarget = realTarget;
        self.timerBlock = block;
    }
    return self;
}

- (void)timerFired:(NSTimer *)timer {
    // Avoid memory leak, timer still run while our real target is dealloc
    if (self.realTarget) {
        self.timerBlock(timer);
    }
    else {
        [timer invalidate];
    }
}

@end

这是我的示例类

@interface MyClass
@property (nonatomic, strong) NSTimer *timer;
@end

@implementation MyClass

- (id)init
{
    self = [super init];
    if (self) {
         // Using __weak for avoiding retain cycles
         __weak typeof(self) wSelf = self;
    EATimerTarget *timerTarget = [[EATimerTarget alloc] initWithRealTarget:self timerBlock: ^(NSTimer *timer) {
        [wSelf onTimerTick:timer];
    }];
         self.timer = [NSTimer timerWithTimeInterval:1 target:timerTarget selector:@selector(timerFired:) userInfo:nil repeats:YES];
         [[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
    }
    return self;
}

- (void)dealloc
{
    [self.timer invalidate]; // This releases the EATimerTarget as well! 
    NSLog(@"### MyClass dealloc");
}

- (void)onTimerTick:(NSTimer *)timer {
     // DO YOUR STUFF!
     NSLog(@"### TIMER TICK");
}

@end

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多