NSTimer 作为自我定位 ivar

发布于 2024-10-12 13:17:47 字数 1134 浏览 2 评论 0原文

我遇到过一个尴尬的情况,我希望有一个带有 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 完全相同,但不保留其目标。我不喜欢它,我会很感激任何见解。

I have come across an awkward situation where I would like to have a class with an NSTimer instance variable that repeatedly calls a method of the class as long as the class is alive. For illustration purposes, it might look like this:

// .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

That's what I want. I don't want to to have to expose an interface on my class to start and stop the internal timer, I just want it to run while the class exists. Seems simple enough.

But the problem is that NSTimer retains its target. That means that as long as that timer is active, it is keeping the class from being dealloc'd by normal memory management methods because the timer has retained it. Manually adjusting the retain count is out of the question. This behavior of NSTimer seems like it would make it difficult to ever have a repeating timer as an ivar, because I can't think of a time when an ivar should retain its owning class.

This leaves me with the unpleasant duty of coming up with some method of providing an interface on MyClock that allows users of the class to control when the timer is started and stopped. Besides adding unneeded complexity, this is annoying because having one owner of an instance of the class invalidate the timer could step on the toes of another owner who is counting on it to keep running. I could implement my own pseudo-retain-count-system for keeping the timer running but, ...seriously? This is way to much work for such a simple concept.

Any solution I can think of feels hacky. I ended up writing a wrapper for NSTimer that behaves exactly like a normal NSTimer, but doesn't retain its target. I don't like it, and I would appreciate any insight.

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

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

发布评论

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

评论(8

喜爱纠缠 2024-10-19 13:17:47

由定时器引起的保留循环是一个令人头疼的问题。我使用过的最不脆弱的方法是保留计时器,但在使其无效时始终将引用设置为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 并完全忽略这些废话。

The retain loops caused by timers are a pain in the neck. The least fragile approach I've used is to not retain the timer, but always set the reference to nil when invalidating it.

@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

The bottom line, though, is that you'll have to have something call -reset for self to be released. A solution is to stick a proxy between self and the timer. The proxy can have a weak reference to self and simply passes the invocation of the timer firing along to self. Then, when self is deallocated (since the timer doesn't retain self and neither does the proxy), you can call invalidate in dealloc.

Or, if targeting Mac OS X, turn on GC and ignore this nonsense entirely.

月隐月明月朦胧 2024-10-19 13:17:47

您可能想看看 NoodleSoft 的 NSTimer 类别,它允许您让计时器运行一个块而不是一个选择器,从而避免将您的类保留为目标(并且还消除了对令人讨厌的代码中不相关的选择器)。请查看此处(其中还有一些您可能会发现有用的其他代码,所以请看一下周围看看你能找到什么)..

You might want to take a look at NoodleSoft's NSTimer category that allows you to have the timer run a block instead of a selector, thereby avoiding retention of your class as target (and also eliminating the need for a pesky unrelated selector in your code). Check it out here (there's also a bunch of other code you might find useful in there, so take a look around and see what you can find)..

满地尘埃落定 2024-10-19 13:17:47

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

首先,我们必须了解 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 需要一些桥接转换。

For the sake of completness, I will post my own solution of a timer which doesn't retain its target. The solution with a proxy is probably the simplest but the one I have is not bad either.

First we have to understand that NSTimer is toll free bridged to a class in Core Foundation: CFRunLoopTimerRef. And Core Foundation classes in general have much bigger possibilites.

@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 is used here, with ARC it needs some bridging casts.

梅倚清风 2024-10-19 13:17:47

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

THInWeakTimer

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

可能值得考虑。

像这样使用它:

有一个 ivar:

THInWeakTimer *_keepaliveTimer;

在你的 init 方法中:

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

I found a nice solution here:

THInWeakTimer

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

Might be worth considering.

Use it like this:

Have an ivar:

THInWeakTimer *_keepaliveTimer;

In your init method:

    __weak FunderwearConnection* wself = self;
    _keepaliveTimer = [[THInWeakTimer alloc] initWithDelay:kIntervalPeriod do:^{
        [wself doSomething];
    }];
一刻暧昧 2024-10-19 13:17:47

在类似的情况下(在 iOS 上,对于 UIView 子类),我搭载了 removeFromSuperview,并且作为双重检查,我在每次计时器触发时测试 .superview 属性。

您确定您没有任何财产或对班级的调用,这实际上意味着它即将消亡吗?

In a similar situation (on iOS, for a UIView subclass) I piggybacked on removeFromSuperview, and as a double check I test the .superview property every time the timer fires.

Are you sure you don't have any property or call on the class which effectively means that it's about to die?

醉酒的小男人 2024-10-19 13:17:47

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

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

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

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

For repeating timer, you do not need the ivar, just:

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

The timer will repeats till end of the program.

For a non-repeating timer, you may check out: performSelector.

南汐寒笙箫 2024-10-19 13:17:47

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

此代码示例正在运行,并确保保留计数始终正确。

自定义设置器的示例

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

    }
}

请记住,每次计时器触发时,您都应该将其设置为零。

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

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

You could use an custom setter to handle this. The setter should release self every time the timers target is self and release everytime it was but is no longer.

This sample of code is working and ensures that the retainCount is correct at all times.

Example of custom setter

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

    }
}

Remember everytime the timer fires you should set it to nil.

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

And in dealloc remember to set self.myTimer = nil

囍孤女 2024-10-19 13:17:47

您还可以使用不保留自身但分配的代理对象..会很干净。

You could also use an proxy-object which does not retain self but assigns.. would be clean.

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