NSTimer作为自我定位的ivar

时间:2011-01-14 03:31:35

标签: objective-c nstimer

我遇到了一个尴尬的情况,我希望有一个带有NSTimer实例变量的类,只要该类处于活动状态,就会重复调用该类的方法。出于说明目的,它可能如下所示:

// .h
@interface MyClock : NSObject {
    NSTimer* _myTimer;
}
- (void)timerTick;
@end

-

// .m
@implementation MyClock

- (id)init {
    self = [super init];
    if (self) {
        _myTimer = [[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(timerTick) userInfo:nil repeats:NO] retain];
    }
    return self;
}

- (void)dealloc {
    [_myTimer invalidate];
    [_myTImer release];
    [super dealloc];
}

- (void)timerTick {
    // Do something fantastic.
}

@end

这就是我想要的。我不想在我的类上公开一个接口来启动和停止内部计时器,我只是希望它在类存在时运行。看起来很简单。

但问题是NSTimer保留了目标。这意味着只要该计时器处于活动状态,它就会使该类不被正常的内存管理方法释放,因为计时器已保留它。手动调整保留计数是不可能的。 NSTimer的这种行为似乎很难让重复计时器成为一个ivar,因为我无法想象一个ivar应该保留其拥有类的时间。

这让我有一种不愉快的责任,想出一些在MyClock上提供接口的方法,该方法允许类的用户控制定时器何时启动和停止。除了增加不必要的复杂性之外,这很烦人,因为让一个类实例的一个所有者使计时器无效可以踩到另一个所有者的脚趾,这个所有者指望它继续运行。我可以实现我自己的伪保留计数系统来保持计时器运行,但......严重吗?对于这样一个简单的概念,这是很有用的方法。

我能想到的任何解决方案都会让人感觉不舒服。我最终为NSTimer编写了一个包装器,其行为与普通的NSTimer完全相同,但不保留其目标。我不喜欢它,我很感激任何见解。

8 个答案:

答案 0 :(得分:4)

由计时器引起的保留循环是颈部疼痛。我使用的最不易碎的方法是保留计时器,但总是在使其无效时将引用设置为nil。

@interface Foo : NSObject
{
    __weak NSTimer *_timer;
}
@end

@implementation Foo
- (void) foo
{
    _timer = [NSTimer ....self....];
}

- (void) reset
{
    [_timer invalidate], _timer = nil;
}

- (void) dealloc
{
    // since the timer is retaining self, no point in invalidating here because
    // that just can't happen
    [super dealloc];
}
@end

但最重要的是,您必须为-reset发布一个名为self的内容。解决方案是在selftimer之间添加代理。代理可以具有对self的弱引用,并且只是将定时器的调用传递给self。然后,当self被解除分配时(由于计时器不会保留self而代理也不会),您可以在invalidate中拨打dealloc

或者,如果定位到Mac OS X,请启用GC并完全忽略此废话。

答案 1 :(得分:2)

你可能想看一下NoodleSoft的NSTimer类别,它允许你让计时器运行一个块而不是一个选择器,从而避免你的类被保留为目标(并且也消除了讨厌的需要)代码中不相关的选择器)。看看here(还有一堆其他代码,你可能会发现它们很有用,所以看看你能找到的东西)..

答案 2 :(得分:2)

我在这里找到了一个很好的解决方案:

THInWeakTimer

https://github.com/th-in-gs/THIn

可能值得考虑。

像这样使用:

有一个ivar:

THInWeakTimer *_keepaliveTimer;

在你的init方法中:

    __weak FunderwearConnection* wself = self;
    _keepaliveTimer = [[THInWeakTimer alloc] initWithDelay:kIntervalPeriod do:^{
        [wself doSomething];
    }];

答案 3 :(得分:1)

为了完整性,我将发布我自己的计时器解决方案,它不会保留其目标。使用代理的解决方案可能是最简单的,但我拥有的解决方案也不错。

首先,我们必须了解NSTimer是免费桥接到Core Foundation中的一个类:CFRunLoopTimerRef。核心基础课程通常有更大的可能性。

@implementation Target

- (void)timerFired:(NSTimer*)timer {
   ...
}

static void timerCallback(CFRunLoopTimerRef timer, void *info) {
    Target* target = (Target*) info;

    [target timerFired:(NSTimer*) timer];
}

- (void)startTimer {
    //timer not retaining its context

    CFRunLoopTimerContext timerContext;
    timerContext.version = 0;
    timerContext.info = self;
    timerContext.retain = NULL;
    timerContext.release = NULL;
    timerContext.copyDescription = NULL;

    //this is a repeating timer
    CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault,
                                                   CFAbsoluteTimeGetCurrent() + FIRE_INTERVAL,
                                                   FIRE_INTERVAL,
                                                   0,
                                                   0,
                                                   timerCallback,
                                                   &timerContext);

    //schedule the timer
    CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);

    self.timer = (NSTimer*) timer;

    CFRelease(timer);
}
@end

这里使用MRC,ARC需要一些桥接演员。

答案 4 :(得分:0)

在类似的情况下(在iOS上,对于UIView子类),我捎带在removeFromSuperview上,作为一个双重检查,我每次计时器触发时都会测试.superview属性。

你确定你没有任何财产或在课堂上打电话有效意味着它即将死去吗?

答案 5 :(得分:0)

对于重复计时器,您不需要ivar,只需:

[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerTick) userInfo:nil repeats:YES];    

计时器将重复直到程序结束。

对于非重复计时器,您可以查看:performSelector

答案 6 :(得分:0)

您可以使用自定义设置器来处理此问题。每次定时器目标是自我时,setter应该释放self,并且每次定时器都会释放,但不再是。

此代码示例正常运行,并确保ret​​ainCount始终正确无误。

自定义设置器的示例

- (void)setMyTimer:(NSTimer *)aNewTimer { // timer retains target/object (self)
    if(aNewTimer != myTimer) {   


    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

        // since myTimer was retaining its target (self) and aNewTimer is not retaining self (aNewTimer is nil) and will not retain self you – should retain self
    if(aNewTimer == nil && myTimer != nil) {
        [self retain];
    }
        // since old timer was not retaining self since myTimer is nil and aNewTimer is retaining self – you should release
    else if(aNewTimer != nil && myTimer == nil) {
        [self release]; 
    }

        [myTimer invalidate];
        [myTimer release];
        myTimer = [aNewTimer retain];

    [pool drain], pool = nil;

    }
}

请记住每次计时器触发时都应将其设置为nil。

- (void)timerDidFire:(NSTimer *)aTimer { 
    if(aTimer == self.myTimer) {
        self.myTimer = nil;
    }
}

在dealloc中记得设置self.myTimer = nil

答案 7 :(得分:0)

你也可以使用一个不保留self但是分配的代理对象..将是干净的。