I have a static method in which I setup NSTimer, but sometimes I need to change repeat time. How to do that if I work with static method? Should be something like this:
+ (void)setupTimer:(BOOL)updateTime newTime:(int)time {
NSTimer *timer;
if (updateTime) {
[timer invalidate];
timer = nil;
}
timer = [NSTimer scheduledTimerWithTimeInterval:time
target:self
selector:#selector(myMethod:)
userInfo:nil
repeats:YES];
}
But it's not working...and I know that I cannot invalidate in this case my timer...but also I cannot use NSTimer declared as member of class in static method...How to be ? thanks....
Just prefix the variable with static.
static NSTimer *timer;
If you overwrite the timer variable, you should invalidate it first, because otherwise you'll have a timer running, that you don't have access to anymore.
I think you should be able to write this without static functions but you can also do this using static functions. If there should be only one instance of that class then use a singleton-pattern.
for doing this with static functions then just use
static NSTimer *timer
but you also have to retain your new and also release the old timer because the function scheduledTimer... allocs an NSTimerinstance but also autoreleases it. If dont retain that timer then the timer will be deallocated later and will afaik not call your method.
Related
I am trying to create an explosion on the iphone screen which gets bigger fast, then goes away. Why is this timer not stopping?
NSTimer *explosion = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(explosion) userInfo:nil repeats:YES];
-(void)explosion {
image.image = [UIImage imageNamed:#"explosion.png"];
expsize = expsize + 2.5;
image.frame = CGRectMake(image.frame.origin.x, image.frame.origin.y, expsize, expsize);
if (expsize > 60) {
NSLog(#"%f",expsize);
[explosion invalidate];
explosion = nil;
}
}
You are most likely invalidating the wrong timer.
You create a local variable named explosion that has the same name as the instance variable.
Avoid declaring instance variables and local variables with the same name!
I'd suggest that you use the form of selector that the NSTimer doc calls for: - (void)timerFireMethod:(NSTimer*)theTimer. The you can invalidate "theTimer" and be sure you're invalidating the right one.
Also, of course, if "explosion" is declared as a property, then there will be two methods in the class named "explosion", and no real clue as to which one is getting called.
It's hard to be certain, because it's not clear whether this is exactly your code, but you have two variables named explosion, and one of them has an NSTimer assigned to it; the other (which seems to be an ivar) is nil.
// Variable local to whatever method this is in
NSTimer *explosion = [NSTimer scheduledTimerWithTimeInterval:0.1...
if (expsize > 60) {
NSLog(#"%f",expsize);
// Other variable named "explosion" does not exist.
// This is an ivar? Has not been set.
[explosion invalidate];
Assuming you've got explosion declared as a property (and there is no reason not to), you should fix this by using the setter when you create the timer:
[self setExplosion:[NSTimer scheduledTimerWithTimeInterval:...]];
Now the ivar has the timer instance and you can use it to invalidate the timer.
Also, note that your timer's method is incorrect; it must take one parameter which is a pointer to the timer. You can also use this pointer to invalidate the timer when it fires.
- (void) fireExplosion: (NSTimer *)tim {
//...
if( expsize > 60 ){
[tim invalidate];
//...
}
}
Finally, you have one last naming problem; if your property is called explosion, the convention in Cocoa is that the accessor should have the same name, but you have used explosion for the method that your timer calls. This could cause hard-to-track problems later. You should rename the timer method as I have here, using a verb indicating that something is happening.
If you are declaring explosion how you posted in your example then you are shadowing your instance variable explosion. As a word of advice you should use a naming convention for instance variables such as an underscore prefix. Now keeping track of the timer is not required if you only invalidate it after it fires. You could just take an extra parameter on the explosion method that would be the timer explosion:(id)timer. Otherwise you can do the following.
#interface X : NSObject
{
NSTimer *_explosion;
}
#end
And when you go to declare it in your code do the following
...
[_explosion invalidate];
[_explosion release];
//There is a whole 'nother debate on whether or not to retain a scheduled timer
//but I am a stickler for ownership so remember to release this in dealloc
_explosion = [[NSTimer scheduledTimerWithTimeInterval:0.1
target:self
selector:#selector(explosion)
userInfo:nil
repeats:YES] retain];
...
-(void)explosion {
image.image = [UIImage imageNamed:#"explosion.png"];
expsize = expsize + 2.5;
image.frame = CGRectMake(image.frame.origin.x, image.frame.origin.y, expsize, expsize);
if (expsize > 60) {
NSLog(#"%f",expsize);
[_explosion invalidate];
[_explosion release];
_explosion = nil;
}
}
I am trying to stop the timer. My problem is when I try to stop the timer the value of timer is nil and it is not getting invalidated. if there any way to stop the timer except this ?
My code is like
timer is defined in header file - and also synthesized.
if([str_Message isEqualToString:ON] // if the command to start timer
{
if(!timer)
{
tmr_CallWebService = [[NSTimer scheduledTimerWithTimeInterval:Time_TracingInterval target:ClassTracing selector:#selector(startLocationTracing) userInfo:nil repeats:YES]retain];
}
//to stop timer
else if([str_Message isEqualToString:OFF])
{
if(timer)
{
[timer invalidate];
timer = nil;
}
Please help me out... thanks in advance
I think you just need to replace tmr_CallWebService with timer.
[str_Message isEqualToString:ON] should be like this [str_Message isEqualToString:#"ON"]. similarly for the comparison with OFF. Use #"OFF".
If you've got a property declared for the timer, you should use it. For instance, if your timer is defined as #property (nonatomic, retain) NSTimer *myTimer; you should refer to it as self.myTimer and set it using that setter and invalidate it later using the accessor.
It looks like you're not setting tmr_CallWebService to your timer ivar at any place so you're just leaking and losing the reference.
What is the correct way to release a NSTimer in my dealloc method ? It was created with the following code ?
-(void)mainTimerLoop {
mainTimer = [NSTimer scheduledTimerWithTimeInterval:1/10
target:self
selector:#selector(gameLoop)
userInfo:nil
repeats:YES];
}
Thanks
The way you're doing it, you won't ever hit dealloc. A timer retains its target. In this case, that means the timer has retained you. It will not release you until it is invalidated. Since you created the timer, you must also invalidate it at some point prior to dealloc, because the timer's retain will prevent your object's being dealloced.
You have two options:
find another place to invalidate the timer (view goes offscreen, application is terminating, what have you)
set something else as the timer's target.
As an example of the latter:
#interface GameLoopTimerTarget : NSObject {
id owner; /* not retained! */
}
- (id)initWithOwner:(id)owner;
- (void)timerDidFire:(NSTimer *)t;
#end
#implementation GameLoopTimerTarget
- (id)initWithOwner:(id)owner_ {
self = [super init];
if (!self) return nil;
owner = owner_;
return self;
}
- (void)timerDidFire:(NSTimer *)t {
#pragma unused (t)
[owner performSelector:#selector(gameLoop)];
}
#end
/* In your main object… */
/* assume synthesized:
#property (retain, NS_NONATOMIC_IPHONE_ONLY) GameLoopTimer *mainTimerTarget; */
- (void)mainTimerLoop {
self.mainTimerTarget = [[[GameLoopTimerTarget alloc] initWithOwner:self] autorelease];
mainTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/10.0 target:self.mainTimerTarget selector:#selector(timerDidFire:) userInfo:nil repeats:YES];
}
- (void)dealloc {
/* other stuff */
[timer invalidate], timer = nil;
[mainTimerTarget release], mainTimerTarget = nil;
/* more stuff */
[super dealloc];
}
Notice how the time interval is 1.0/10.0 - this could also be written 0.1, but it cannot be written 1/10, as that division will truncate to 0.0.
Also notice how this breaks the retain cycle:
Both you and your timer retain the timer target.
You hit dealloc at the normal time.
You then invalidate the timer and release the timer target.
The timer target is then deallocated.
A valid NSTimer is retained by the run loop, which, if it is repeating, will be forever or until you invalidate it. You shouldn't release it, since, in your example code, you did not explicitly retain it. If you invalidate it, it will no longer be retained by the run loop, and will be autoreleased.
This might be OK for a repeating timer, but is dangerous for a one-shot timer, since it might end being released before you ever access it to see if it's valid and/or try to invalidate it (which would lead to a bad-access app crash). Therefore if you plan on, in any way, looking at a timer variable after it's creation (including to check it, invalidate it and/or release it), it might be a good practice to explicitly retain it somewhere in your app, and then release it and set it to nil after it's invalid and you are done with it.
You can release it and set it to nil in one statement if you declare it as a retain property. Then you can write:
self.timer = nil;
you have a really good answer about NSTimer here How do I use NSTimer? there they talk about stoping a repeating NSTimer doing
[myTimer invalidate];
I think the best advice here is -
Do not retain the NSTimer instance, and do not release it.
As soon as it is scheduled on an NSRunloop (current runloop in the OP's example, an NSTimer is retained by the runloop until being invalidated, or until the runloop stops.
What you should be doing, is to invalidate your timer at the right time - and on the same thread where you created and scheduled it.
Keep in mind, also, that NSTimer retains its target, and won't let the target "die" before it dies itself. design your code so that you don't have a retain cycle that will prevent the releasing of both your object (holding the timer) and the timer (holding you object).
You don't need to release it because it will be autoreleased. Anything created by a convenience method (i.e. you don't call alloc yourself) is the responsibility of the called function to memory manage, which usually means that it will call autorelease on the object it creates before it returns it.
I would assign the timer to a property with the retain keyword though to make sure it doesn't get deallocated on you. Generally autoreleased objects are deallocated in the event loop if they don't have any retains.
I'd like to create an NSTimer that repeats twice then stops and invalidates itself. I'd rather not use a loop if that's possible. Any thoughts on how I could do this?
Create a static int inside your timer delegate function that is initialized to 0.
Increment it each time the delegate is called.
When the counter reaches the value you wish invalidate the timer.
This is something your timer's target should handle, not something the timer itself should handle. You can either install a repeating timer and have the target invalidate it the second time it fires, or you can install a one-shot timer, reinstall it after the first time it fires, and then not set it up again the second time.
Basically, you need a state machine state variable that can be accessed both from the routine that initializes the timer, and from the timer's target.
Set the state variable to allow the first call to the timer task to restart the timer, but in that call also set that state variable so that subsequent calls do not restart.
Note that this kind of state variable can be used for any number of timer task repetitions, by simply decrementing it.
State machines are pretty much how all (synchronous) digital chips and logic works.
I very much disagree with the Jeremy that this is something that the target should handle. In fact I disagree so much that I have created my own Timer class, based on NSTimer, that you can configure in detail.
- (void) doSomething: (Timer*) timer
{
NSLog(#"This is iteration %d", timer.currentIteration);
}
- (void) startDoingSomething
{
Timer* timer = [Timer new];
timer.interval = 5.0; // Fire every 5 seconds
timer.delay = 2.5; // Start firing after 2.5 seconds
timer.iterations = 3; // Only fire three times
timer.target = self;
timer.selector = #selector(doSomething:);
[timer schedule];
// Don't forget to release timer somewhere - the above is just an example
}
See http://github.com/st3fan/ios-utils
One solution might look similar to this.
Launching the timer
[NSTimer scheduledTimerWithTimeInterval:3 target:self selector:#selector(timerMethod:) userInfo:nil repeats:YES];
Handling the timer and repetitions
int repetitions = 2; //EDIT: remove static declaration (see below)
- (void)timerMethod:(NSTimer*)theTimer{
NSLog(#"Timer fired");
repetitions--;
if(repetitions == 0){
[theTimer invalidate];
NSLog(#"Timer stopped");
}
}
EDIT:
I removed the static modifier above to make a more generic example. The original intent of the static was to persist the timer across objects of similar type, a request that the OP did not make.
I have a Class with a NSTimer *myTimer; variable. At some point I do:
myTimer = [NSTimer scheduledTimerWithTimeInterval:20 target:self selector:#selector(doStuff) userInfo:nil repeats: YES];
further, I have a method:
- (void)doStuff
{
if(myTimer)
{
//do stuff
}
}
and I stop my timer when the class is released through:
- (void)dealloc
{
if (myTimer) { //if myTimer==nil it already has been stopped in the same way
[myTimer invalidate];
myTimer = nil;
}
}
Now, the problem is that when I release the class the timer goes on and on and on firing the event anyway. Am I doing something wrong? It seems the dealloc method is never called, otherwise myTimer would be nil and even if the selector is fired it would not go into the if(myTimer)
This will never work, because timers retain their target, which means your dealloc method will never get invoked until after you've invalidated the timer.
For more info, see the NSTimer documentation and this blog post on "Dangerous Cocoa Calls"
Have you tried the handy debugger tools at your disposal? What happens if you set a breakpoint in your dealloc method? Also, you should post more context around your creation. Is it possible you're creating the timer more than once, thereby replacing the original (but not invalidating it) and leaving it out there to fire at will?