简体   繁体   中英

Retaining repeating NSTimer for later access?

I am creating an NSTimer in the createTimer method that I want to refer back to in the later cancelTimer method. To facilitate this I am taking ownership of the NSTimer via a retained property so that I can refer back to it later. The issue that is confusing me is, if I start the timer, cancel it and start it again the code crashes.

@property(nonatomic, retain) NSTimer *walkTimer;

.

-(void)createTimer {
    NSTimer *tempTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(updateTimerDisplay) userInfo:nil repeats:YES];
    [self setWalkTimer:tempTimer];
}

-(void)cancelTimer {
    [walkTimer release];
    [[self walkTimer] invalidate];
}

Now I seem to have fixed this by changing cancelTimer to:

-(void)cancelTimer {
    [self setWalkTimer:nil];
    [[self walkTimer] invalidate];
}

I am just curious why release was not working, my understanding was that:

  1. NSTimer (Autorelease object, not owned by me)
  2. setWalkTimer (takes ownership for me, retainCount+1)
  3. release (relinquishes my ownership, retainCount-1)
  4. invalidate (lets system dispose of timer)

EDIT:

// this fails ...
-(void)cancelTimer {
    [[self walkTimer] invalidate];
    [walkTimer release];
}

// this works fine ...
-(void)cancelTimer {
    [[self walkTimer] invalidate];
    [self setWalkTimer: nil];
}

EDIT: 002

Initially I think I was mixing up

@property(nonatomic, retain) NSTimer *walkTimer;
// &
[self setWalkTimer];

and thinking that I needed a release to balance the property, I don't I either overwrite it with a new set (either to another object or nil) and finally release the property in dealloc.

Is the property(retain) the same as retain, I would say no, which is where I was going wrong I think.

EDIT: 003 With regards to this question I think I personally confused things by wrongly using [walkTimer release] As a result the topic drifted to essentially a new question which I have written up as this

You release before you call invalidate . That means by the time you call invalidate , you've already relinquished ownership of the timer. In practice, you end up calling invalidate on a deallocated timer instance.

What you should do is call invalidate before you call release . Since you are using a retained property, you can just set the property to nil :

// Schedule the timer.
self.walkTimer = [NSTimer scheduledTimerWith...];

// Cancel the timer.
[self.walkTimer invalidate];
self.walkTimer = nil;

Update to clear up any confusion regarding memory management

It's important to keep in mind the Memory Management Rules of Objective-C — you own an object if you call alloc , copy or retain on it, and if you own an object, you have to eventually call release . In this case, setWalkTimer: retains the timer because the property is declared as retain — that means you own the timer and must call release on it down the road. The invalidate method does not count as relinquishing ownership of the timer.

When you schedule a timer, the run loop retains it, and when the timer fires or is invalidated, the run loop releases it. But really, you don't need to know that — it's an implementation detail. The call to release by invalidate is only to balance the retain when the timer was scheduled on the run loop.

You need to invalidate before you release. After the timer has fire, you are the only one holding a retain on the timer. So when you call release, the timer deallocates. You then call invalidate on invalid memory and you crash.

Don't retain a scheduled NSTimer if you've set its target to self Don't set self as the target of a repeating timer unless you are absolutely sure to know all the consequences

(...otherwise the runtime drowns a kitten in leaked timers, targets and userInfos — or so goes the saying.)

Please read and re-read "Overview" in the NSTimer Class Reference and pay special attention to the last paragraph.

In a nutshell:

  1. If you schedule an NSTimer, it becomes associated to the current run-loop which retains it.
  2. Furthermore, the timer retains its target .
  3. NSTimer instances are not reusable : "Once invalidated, timer objects cannot be reused" .

So there is no point in retaining a scheduled timer in the first place.

If you need to hang on to it (eg in order to cancel it) use a non-owning (aka weak) reference to it.

Update:
For a thorough explanation, see my answer to your other question (it now has graphs — albeit as links only — and stuff).

Please consider the rest of this post (as well as many of my comments) as obsolete.


Your property becomes

@property (nonatomic, assign) NSTimer *walkTimer;

BTW:

-(void)cancelTimer {
    [self setWalkTimer:nil]; // great, now [self walkTimer] returns nil so
    [[self walkTimer] invalidate]; // here, you are calling [nil invalidate]
}

And since messaging nil is absolutely fine in Objective C, your crash miraculously vanishes...while your timer will happily continue to fire.

Edit

I've forgot to mention:
A timer wants a selector that takes one argument, which will be the timer that fired... Or is that just a typo?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM