开发者

NSTimers causing leaks

I've read up a lot about NSTimers, but I must be doing something very wrong with them, because it's practically all the leaks that show up in the Leaks Instrument. The "Responsible Frame" column says -[NSCFTimer or +[NSTimer(NSTimer).

So here's how I have an NSTimer set up in my main menu. I shortened it up to just show how the t开发者_StackOverflowimer is setup.

.h -

@interface MainMenu : UIView {
    NSTimer *timer_porthole;    
}

@end


@interface MainMenu ()

-(void) onTimer_porthole:(NSTimer*)timer;


@end

.m -

(in initWithFrame)

- (id)initWithFrame:(CGRect)frame {

    self = [super initWithFrame:frame];
    if (self) {

        timer_porthole = [[NSTimer scheduledTimerWithTimeInterval:.05
                                                           target:self
                                                         selector:@selector(onTimer_porthole:)
                                                         userInfo:nil
                                                          repeats:YES] retain];

    }
    return self;
}   

When leaving the view, it kills the timers:

-(void) kill_timers{
     [timer_porthole invalidate];
     timer_porthole=nil;
}

And of course, dealloc:

- (void)dealloc {
    [timer_porthole invalidate];
    [timer_porthole release];
    timer_porthole = nil;

    [super dealloc];
}


Don't call retain on your NSTimer!

I know it sounds counter-intuitive but when you create the instance it's automatically registered with the current (probaby main) threads run loop (NSRunLoop). Here's what Apple have to say on the subject...

Timers work in conjunction with run loops. To use a timer effectively, you should be aware of how run loops operate—see NSRunLoop and Threading Programming Guide. Note in particular that run loops retain their timers, so you can release a timer after you have added it to a run loop.

Once scheduled on a run loop, the timer fires at the specified interval until it is invalidated. A non-repeating timer invalidates itself immediately after it fires. However, for a repeating timer, you must invalidate the timer object yourself by calling its invalidate method. Calling this method requests the removal of the timer from the current run loop; as a result, you should always call the invalidate method from the same thread on which the timer was installed. Invalidating the timer immediately disables it so that it no longer affects the run loop. The run loop then removes and releases the timer, either just before the invalidate method returns or at some later point. Once invalidated, timer objects cannot be reused.

  • Quotes are sourced from Apple's NSTimer class reference.

So your instantiation becomes...

timer_porthole = [NSTimer scheduledTimerWithTimeInterval:.05
                                                           target:self
                                                       selector:@selector(onTimer_porthole:)
                                                         userInfo:nil
                                                          repeats:YES];

And now that you're no longer holding the reference to the instance you wont want the release call in your dealloc method.


I've seen you already accepted an answer but there are two things here that I wanted to rectify:

  1. It's not needed to retain a scheduled timer but it doesn't do any harm (as long as you release it when it's no longer needed). The "problematic" part of a timer/target relationship is that...
  2. a timer retains its target. And you've decided to set that target to self.
    That means — retained or not — the timer will keep your object alive, as long as the timer is valid.

With that in mind, let's revisit your code from bottom to top:

- (void)dealloc {
    [timer_porthole invalidate]; // 1
    [timer_porthole release];
    timer_porthole = nil;  // 2

    [super dealloc];
}

1 is pointless:
If timer_porthole was still a valid timer (i.e. scheduled on a runloop) it would retain your object, so this method wouldn't be called in the first place...

2 no point here, either:
This is dealloc! When [super dealloc] returns, the memory that your instance occupied on the heap will be freed. Sure you can nil out your part of the heap before it gets freed. But why bother?

Then there is

-(void) kill_timers{
     [timer_porthole invalidate];
     timer_porthole=nil; // 3
}

3 given your initializer (and as others have pointed out) you are leaking your timer here; there should be a [timer_porthole release] before this line.


PS:

If you think it all over, you'll see that retaining the timer (at least temporarily) creates a retain-cycle. In this particular case that happens to be a non-issue which is resolved as soon as the timer is invalidated...


You missed [timer_porthole release]; call in your kill_timers method. If you call kill_timers before dealloc method is called, you set timer_porthole to nil, but you did not release it.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜