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.
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.
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)..
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];
}];
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With