So I've got a timer that is not repetitive. Each time it fires, the method that being executed decide if to reschedule it or not according to some inner logic of my app.
This method is available from other parts of the app, so the first thing that I'm doing in the method is to check if the timer is still valid (to know if the initiator was the timer or a different entity) so in case it wasn't initiated by the timer I want to invalidate it:
if (self.pollingTimer.isValid) {
[self.pollingTimer invalidate];
self.pollingTimer = nil;
}
I've noticed that if the method is being called due to the timer being fired - I always receive a true value from the isValid property, even though when looking at the NSTimer documentations under the scheduledTimerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats method:
repeats
If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.
Discussion
After seconds seconds have elapsed, the timer fires,
sending the message aSelector to target.
I'm having hard time to understand when the timer is being automatically invalidated which bring me to my questions:
Any idea why I always get YES from isValid?
What is the exact definition of the timer fires? Is it just sending the message aSelector to target as stated in the documentation? or is it finishing the execution of the method? (which might explain what I'm experiencing)
Thanks in advance.
A timer is not a real-time mechanism; it fires only when one of the run loop modes to which the timer has been added is running and able to check if the timer’s firing time has passed. Therefore, the timer does not immediately invalidate itself, but at the end of the run loop.
As a simple test, you can see:
- (void)viewDidLoad {
[super viewDidLoad];
_timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(timerFired) userInfo:nil repeats:NO];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (self.timer.isValid){
NSLog(#"--> TIMER VALID");
} else {
NSLog(#"TIMER INVALID!");
}
});
}
- (void) timerFired {
if (self.timer.isValid){
NSLog(#"--> TIMER VALID");
} else {
NSLog(#"TIMER INVALID!");
}
}
This will log --> TIMER VALID from the timerFired method and when the block from dispatch_after is called, you will see TIMER INVALID!. So, when you schedule a timer with repeats:NO, it is guaranteed to not reschedule itself but it will not invalidate immediately.
So, to answer your question:
repeats
If YES, the timer will repeatedly reschedule itself until
invalidated. If NO, the timer will be invalidated after it fires (but not immediately)
I made a test like this:
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(xx) userInfo:nil repeats:NO];
- (void)xx
{
if ([self.timer isValid]) {
NSLog(#"isvalid");
}
dispatch_async(dispatch_get_main_queue(), ^{
if ([self.timer isValid]) {
NSLog(#"isvalid");
}else{
NSLog(#"isInvalid");
}
});
static int i = 0;
NSLog(#"%d",i++);
}
and the result is:
isvalid
0
isInvalid
thus, I guess when timer is fired,the function is execute like this:
void __NSFireTimer(){
id yourObj;
[yourObj performSelector:#selector(yourSelector)];
timer.isvalid = NO;
}
what you believe is:
void __NSFireTimer(){
id yourObj;
timer.isvalid = NO;
[yourObj performSelector:#selector(yourSelector)];
}
So, just accept it.You can put your check valid code in dispatch_asyn() ,like the test code.
This is how I used my timers. First initialise it on the top as
#property (strong, nonatomic) NSTimer *refreshTimer;
then this two methods, to create and invalidate the timer. "Its very important to invalidate the current timer if you want to create another timer with same name" otherwise their will be two timers.
- (void)startTimer {
if (_refreshTimer) {
[self invalidateTimer];
}
_refreshTimer = [NSTimer scheduledTimerWithTimeInterval:15.0
target:self
selector:#selector(determineIfPartOfgroup)
userInfo:nil
repeats:YES];
}
- (void)invalidateTimer {
if (_refreshTimer) {
[_refreshTimer invalidate];
_refreshTimer = nil;
}
}
I hope this will help you.
Related
A strange situation:
If I started my Timer again and again without stopping it first, it will count increasingly fast. I guess it is because it starts multiple timers now?
However, when I finally want to stop it, it cannot be stopped...keep going forever.
(Maybe for design consideration, I should disable users from pressing start again, but I'm wondering what is really behind this and why the timer can't be stopped.)
- (IBAction)Start:(id)sender {
countInt = 0;
self.Time.text = [NSString stringWithFormat:#"%i", countInt];
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(countTimer) userInfo:nil repeats:YES];
}
- (IBAction)Stop:(id)sender {
[timer invalidate];
}
- (void) countTimer {
countInt += 1;
self.Time.text = [NSString stringWithFormat:#"%i", countInt];
}
#end
The simple solution is to call stop at the beginning of the start method.
Note that in stop you should also set timer = nil;
Assuming there is a property timer
#property NSTimer *timer;
the most reliable way to start and stop the timer only once respectively is to create two methods.
- (void)startTimer
{
if (self.timer == nil) {
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(countTimer)
userInfo:nil
repeats:YES];
}
}
- (void)stopTimer
{
if (self.timer != nil) {
[self.timer invalidate];
self.timer = nil;
}
}
Both methods perform a check, so the timer can't be restarted while it's running and vice versa.
Now just call the methods in the start/stop IBActions (the names should start with a lowercase letter).
- (IBAction)Start:(id)sender {
countInt = 0;
self.Time.text = [NSString stringWithFormat:#"%i", countInt];
[self startTimer];
}
- (IBAction)Stop:(id)sender {
[self stopTimer];
}
The benefit is pressing Start has no effect when the timer is already running.
When you hit 'start' multiple times you are creating multiple timers. So you are getting multiple timers firing and executing your timer callback. In this timer callback you increment counters. Since there are many timers now, they are all incrementing your counter, hence explaining your rapid increase in the counter.
You can allow the user to tap Start twice, as long you can define what happens when you hit Start while the timer is already going. But you definitely need to invalidate the old timer before creating a new one.
- (IBAction)Start:(id)sender {
...
// Stop previous timer before creating a new timer.
if (timer != nil) {
[timer invalidate]
}
...
}
My timer does not stop even if i am doing "invalidate" and "nil" after reading other links. My code is as below:
#property(nonatomic,strong) NSTimer *mytimer;
- (void)viewDidLoad {
[self performSelectorOnMainThread:#selector(updateProgressBar:) withObject:nil waitUntilDone:NO];
<do some other work>
}
- (void) updateProgressBar :(NSTimer *)timer{
static int count =0;
count++;
NSLog(#"count = %d",count);
if(count<=10)
{
self.DownloadProgressBar.progress= (float)count/10.0f;
}
else{
NSLog(#"invalidating timer");
[self.mytimer invalidate];
self.mytimer = nil;
return;
}
if(count <= 10){
NSLog(#"count = %d **",count);
self.mytimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(updateProgressBar:) userInfo:nil repeats:YES];
}
}
1) The timer goes on infinetly even when invalidating timer else condition is hit after count >10 and count keeps on incrementing.
2) i want to do this on a non-main thread . i want to continue in viewdidload() after starting the timer. How to do this ?
I visited other links on SO, all i understood was to call invalidate and nil on timer pointer. I am still facing problems. Could anyone tell me what i am missing here and what i can i do to run the updateProgressBar on background thread and update the progress bar ?
don't need to schedule a timer each time, schedule it once and timer will fire every second for example u can do like below,
- (void)viewDidLoad
{
[super viewDidLoad];
[self performSelectorOnMainThread:#selector(startTimerUpdate) withObject:nil waitUntilDone:NO]; //to start timer on main thread
}
//hear schedule the timer
- (void)startTimerUpdate
{
self.mytimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(updateProgressBar:) userInfo:nil repeats:YES];
}
- (void) updateProgressBar :(NSTimer *)timer{
static int count =0;
count++;
NSLog(#"count = %d",count);
if(count<=10)
{
//self.DownloadProgressBar.progress= (float)count/10.0f;
NSLog(#"progress:%f",(float)count/10.0f);
}
else
{
NSLog(#"invalidating timer");
[self.mytimer invalidate];
self.mytimer = nil;
return;
}
if(count <= 10){
NSLog(#"count = %d **",count);
}
}
I think you are scheduling timer multiple time. I think 10 time. just schedule time one time or if require many time then invalidate it that many time as schedule.
Update according to comment : Schedule timer from viewdidload and addobserver means notification on task. when your task will completed invalidate timer. and update your progress in selector method of timer so when you invalidate it it will automatically stop progress bar.
Second thing : you should invalidate timer before moving another viewcontroller also because this objects remains live untill invalidate.
Hope this will hellp :)
I use this code for stopping NSTimer
[timer invalidate]
timer = nil;
It works fine for the first run. But, after I resume the timer with this code.
timer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:#selector(checkNewUpdates)
userInfo:nil
repeats:YES];
NSTimer won't stop anymore with [timer invalidate]
It look like multiple instance of timer is running simultaneously. You can do one thing, before start to run a new timer, check for previous instance of timer, and if timer instance is available, then invalidate it. After this start new instance
if(timer)
{
[timer invalidate];
timer = nil;
}
timer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:#selector(checkNewUpdates)
userInfo:nil
repeats:YES];
In apple's official document they said:
You must send this message from the thread on which the timer was
installed. If you send this message from another thread, the input
source associated with the timer may not be removed from its run loop,
which could prevent the thread from exiting properly.
If your timer is running on main thread, do this:
[timer performSelectorOnMainThread:#selector(invalidate) withObject:nil waitUntilDone:YES];
If it is on any other thread, lets call the thread myThread, then do this:
[timer performSelector:#selector(invalidate) onThread:myThread withObject:nil waitUntilDone:NO];
Hope this helps.. :)
Just invalidate the timer inside the selector that fires. That will ensure you have a pointer to the correct timer (which is probably why your invalidate call isn't working:
timer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:#selector(checkNewUpdates:)
userInfo:nil
repeats:YES];
Note the colon after checkNewUpdates:
Then, in your checkNewUpdates: method, do something like this:
- (void)checkNewUpdates:(NSTimer*)timer
{
// do somehting
// Then, check if the criteria for stopping the timer has been met, and invalidate it here.
if( self.shouldStopTimer ) // made up variable, use your own criteria.
{
[timer invalidate];
}
}
I know this doesnt answer your question per-se;
Can I suggest using polling mechanism instead of a timer? Ive had a world of trouble with NSTimers in the past and polling was a good alternative. Ive made a simple one below.
- (void) doPoll {
// do logic here
if (shoudStop) {
return;
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, X * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
[self doPoll];
});
}
This is just a simple example, it does not stop retain cycles If you choose to try this, Yours should.
Hope it helps.
#property (weak, nonatomic) NSTimer *timer;
-void()timerMethod
{
...some stuff
[self.timer invalidate];
self.timer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(refreshScreen:) userInfo:nil repeats:NO];
}
-void(refreshScreen:id(sender)
{
...some stuff that calls other methods one of which calls timerMethod to continue the process
}
-(void)viewWillDisappear:(BOOL)animated
{
[self.timer invalidate];
self.timer = nil;
}
When I move to a new VC, viewWillDisappear is called and I confirm that
1/ timer is invalidated (using [self.timer isValid].
2/ We are on the main thread (using [NSThread isMainThread]
3/ timer is Nil
The timer itself when created is also confirmed on the main thread.
Some seconds later on the other VC, the timer calls the "refreshScreen" method regardless of the above.
I have checked that the invalidate within timerMethod is invalidating with each pass. I am doing this to ensure that more than 1 timer is not created. Breakpoints show that although the timer goes invalid and nil upon exit, once it calls "refreshScreen" again it has reestablished the timer and is no longer nil despite the fact that its VC was dismissed.
self.timerProgress=[NSTimer scheduledTimerWithTimeInterval:50.0 target:self selector:#selector(stopProgressView) userInfo:nil repeats:NO];
-(void)stopProgressView
{
if ([self.timerProgress isValid]) {
[self.timerProgress invalidate];
self.timerProgress=nil;
}
}
and on a button click when i tried to invalidate NSTimer object
-(void)cancelTimer
{
if ([self.timerProgress isValid]) {
[self.timerProgress invalidate];
self.timerProgress=nil;
}
}
it don't get invalidate. It calls once stopProgressView after the interval of 50.
How to get resolve from this issue?
- (IBAction)stopTimer {
if ([timerProgress isValid]) {
[timerProgress invalidate];
}
}
Don't use self.timerProgress use just timerProgress
The most likely reason for this is that your timer scheduled on a different run loop to the one where you try and invalidate it.
Timers must be invalidated on the same thread/runloop as the run loop that they are scheduled on.
Cocoa touch isn't thread safe, so you should be running all UI related activities on the main thread. It may work if you do GUI work on different threads, but then you'll get random crashes, and you'll also generate timer problems like this.
It seems like from what you're posting it should work. This is how I have it in my apps and it works fine.
However, you could try making the selector one that takes a timer object like:
-(void)stopProgressView:(NSTimer *)timer{
//do stuff with timer here
}
Note that this would also mean that you should change #selector(stopProgressView) to #selector(stopProgressView:). Although for the record my current stop timer function just uses [self.timer invalidate] and it works fine.
My other piece of advice for debugging is to use NSLogs to make sure each of the methods are in fact getting called, and that when the method is called an NSLog within the if clause to make sure that works.
You create NSTimer with out NSRunLoop so your NSTimer not started, to add this code after
self.timerProgress = [NSTimer scheduledTimerWithTimeInterval:50.0
target:self
selector:#selector(stopProgressView)
userInfo:nil
repeats:NO];
//add
[[NSRunLoop currentRunLoop] addTimer:_tapTimer
forMode:NSDefaultRunLoopMode];