NSTimer Will Not Invalidate - objective-c

-(IBAction) loadWeb: (id) sender {
[_webView loadRequest:nsrequest2];
_webView1.hidden = YES;
_webView.hidden = NO;
self.checkForAdd = [NSTimer scheduledTimerWithTimeInterval:0.4
target:self selector:#selector(checkForAddToCart:) userInfo:nil
repeats:YES];
}
-(IBAction)button1:(id)sender {
[self.checkForAdd invalidate];
}
How would I invalidate the timer? I have tried it without self and many other ways, but for some reason, when I press the button the timer does not invalidate.

Check if loadWeb: is called multiple times. If it is, you will have old timers running without having a reference to them so you can't invalidate them. You should have [self.checkForAdd invalidate]; before you create the new timer.
When you do invalidate the timer, if you aren't creating a new one, set self.checkForAdd = nil; to be sure you aren't going to try using the timer again (some actions will throw an exception if you do).
If at any point you do self.checkForAdd = nil; without having invalidated the timer then you won't have a reference to it so you won't be able to invalidate it in the future.

According to your code, loadWeb is being trigger by a button. So you will be creating new timer every time when button will be press. Its better you create timer on some where else, like create in init, or in viewDidLoad method, because if you are creating this here, you have to make sure you are not creating timer again and again. You can do this by doing a if check
if(!self.checkForAdd){
self.checkForAdd = [NSTimer scheduledTimerWithTimeInterval:0.4
target:self selector:#selector(checkForAddToCart:) userInfo:nil
repeats:YES];
}
Right now its creating new timer on button tap, and previous one have no reference.

Related

Xcode - NSTimer firing at random intervals? No consistency

So I am using NSTimer to run a function every minute, it fires at the correct time for the first 3 or so attempts and then it suddenly starts firing every second. I have no idea why this is happening? Would anyone be able to let me know as to why NSTimer is firing inconsistently?
Here is the line where I have declared my timer.
[NSTimer scheduledTimerWithTimeInterval:60.0f target:self selector:#selector(checkForLocation) userInfo:nil repeats:YES];
It is worth noting that I have NSTimer declared inside of viewDidAppear.
Any help is appreciated,
Thank you.
Try to create a instance for NSTimer by declaring a property. Write a function which will initialize the timer and don’t forget to invalidate it before re-intializing it. Call initializeMyTimer in your -viewDidAppear.
-(void) initializeMyTimer
{
if(myTimer)
{
[myTimer invalidate];
myTimer = nil;
}
myTimer = [NSTimer scheduledTimerWithTimeInterval:60.0f
target:self
selector:#selector(checkForLocation)
userInfo:nil
repeats:YES];
}

NSTimer's action method is not being called

I have the following code to create an NSTimer which should update a label each time it fires:
.h file
#interface Game : UIViewController
{
NSTimer *updateTimer;
UILabel *testLabel;
int i;
}
-(void)GameUpdate;
#end
.m file
#implementation Game
-(void)GameUpdate
{
i++;
NSString *textToDisplay = [[NSString alloc] initWithFormat:#"Frame: %d", i];
[testLabel setText:textToDisplay];
NSLog(#"Game Updated");
}
- (void)viewDidLoad
{
[super viewDidLoad];
[[UIApplication sharedApplication] setStatusBarHidden:YES animated:NO];
updateTimer = [NSTimer scheduledTimerWithTimeInterval:0.01428 target:self selector:#selector(GameUpdate) userInfo:nil repeats:YES];
}
//other methods (viewDidUnload, init method, etc.)
#end
When I run it, a label appears in the top that says "0" but does not change. It makes me believe I missed something in how the NSTimer is to be setup. What did I miss?
I used breakpoints and (as you can see) logging to see if the method is actually running, rather than some other error.
I was having a similar problem, and it had a different root cause, related to the run loop. It's worth noting that when you schedule the Timer with the code:
updateTimer = [NSTimer scheduledTimerWithTimeInterval:0.01428 target:self selector:#selector(GameUpdate) userInfo:nil repeats:YES];
The timer will get scheduled with the current thread's runLoop. In your case, because you make this call within the viewDidLoad, it is the main thread, so you are are good to go.
However, if you schedule your timer with a thread other than the main thread, it will get scheduled on the runLoop for that thread, and not main. Which is fine, but on auxiliary threads, you are responsible for creating and starting the initial run loop, so if you haven't done that - your callback will never get called.
The solution is to either start the runLoop for your auxiliary thread, or to dispatch your timer start onto the main thread.
to dispatch:
dispatch_async(dispatch_get_main_queue(), ^{
updateTimer = [NSTimer scheduledTimerWithTimeInterval:0.01428 target:self selector:#selector(GameUpdate) userInfo:nil repeats:YES];
});
To start a runloop:
After creating a thread using your API of choice, call CFRunLoopGetCurrent() to allocate an initial run loop for that thread. Any future calls to CFRunLoopGetCurrent will return the same run loop.
CFRunLoopGetCurrent();
updateTimer = [NSTimer scheduledTimerWithTimeInterval:0.01428 target:self selector:#selector(GameUpdate) userInfo:nil repeats:YES];
Your callback must have this signature:
-(void)GameUpdate:(NSTimer *)timer
This is explicitly in the docs. And the #selector() reference when you setup the timer should be #selector(GameUpdate:) (notice the trailing :).
Try that.
Just in case anyone stumbles across this, I want to point out that this:
[[NSString alloc] initWithFormat:#"Frame: %d", i];
Needs memory management.
Safely replace with:
[NSString stringWithFormat:#"Frame: %d", i];
for the same effect but no need for memory management.
P.S. At time of writing I cannot comment on the original post, so I've added this as an answer.
EDIT: As adam waite pointed out below, this isn't really relevant anymore with the widespread usage of ARC.
I have had a little bit different issue with NSTimer - scheduled method call was ignored during UITableView scrolling.
Timer had been started from main thread. Adding timer explicitly to main run loop resolved the problem.
[[NSRunLoop mainRunLoop] addTimer:playbackTimer forMode:NSRunLoopCommonModes];
Solution found here https://stackoverflow.com/a/2716605/1994889
UPD: CADisplayLink fits much better for updating UI.
According official documentation, CADisplayLink is a:
Class representing a timer bound to the display vsync.
And can be easily implemented like:
playbackTimer = [CADisplayLink displayLinkWithTarget:self selector:#selector(updateUI)];
[playbackTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
and removed like
if (playbackTimer) {
[playbackTimer removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
playbackTimer = nil;
}

iOS5 ARC is it safe to schedule NSTimers from background selectors?

I'm trying to debug my application.
I've been using some NSTimer instances in my non-arc code like this (from the main thread):
[NSTimer scheduledTimerWithTimeInterval:5 target:musicPlayer selector:#selector(playPause:) userInfo:nil repeats:NO];
This works fine if I assign this code to a button and click a button. The timer fires.
I've also tried:
if( self.deliveryTimer == nil)
{
self.deliveryTimer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(playPause:) userInfo:nil repeats:NO];
}
-(void)playPause:(NSTimer*)timer
{
[deliveryTimer invalidate];
deliveryTimer = nil;
//more code here
}
I would expect the timer to execute, hit the play/pause method below, then turn to nil, so I can reset the timer later. The reason why I'm checking for nil is because I have 3 different code paths that may set the timer. Each one has an NSLog statement indicating that the timer has been scheduled.
My code runs, and I see that the timers are being scheduled, but they don't seem to fire in the course of normal app execution. I'm investigating why. Short term timers, using the same logic fire fine. It is when I let the app run for a while that I'm running into issues.
Could the NSTimers be reclaimed by ARC?
Does it matter if I set the timer from a performSelectorInBackground? As I was writing up this question, I noticed that some of my timers were created from a code path that is being called through:
[self performSelectorInBackground:#selector(notifyDelegateOfDataPoint:) withObject:data];
could the background selector be the reason why my timers do not fire/get reclaimed earlier?
Any help is appreciated, this bug has been bugging me for over 2 weeks!
Update: after changing the code to use the main thread for NSTimers, the timers fire correctly, causing the music to play:
[self performSelectorOnMainThread:#selector(deliverReminder:) withObject:nil waitUntilDone:NO];
-(void)deliverReminder:(id)sender{
[ NSTimer scheduledTimerWithTimeInterval:10 target:reminderDeliverySystem selector:#selector(playAfterDelay:) userInfo:nil repeats:NO];
[self postMessageWithTitle:nil message:#"Deliver Reminder Called" action:kNoContextAction];
}
-(void)playAfterDelay:(id)sender
{
int reminderDelay = reminder.delayValue.intValue;
[playTimers addObject:[NSTimer scheduledTimerWithTimeInterval:reminderDelay target:self selector:#selector(appMusicPlayerPlay:) userInfo:nil repeats:NO]];
}
Here I have a whole bunch of timers, which is because I don't know how to pass a primitive to a target with a selector.
An NSTimer requires a run loop to be running in that background thread for it to keep firing. The main thread already has an active run loop, which is why your timers work fine when executed on it.
If you want to use your timers within a background thread, you can do something like the following:
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
self.deliveryTimer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(playPause:) userInfo:nil repeats:NO];
[runLoop run];
What was probably happening with your short duration timers firing, but the longer ones not, was that they were firing while the thread was still active, but without a run loop to keep it going, were failing after the thread reached the end of its execution.
I don't believe this is ARC-related, although there may be something there you'll have to watch for, because the NSRunLoop holds on to a timer that is attached to it. Following standard procedure with NSTimers should avoid ARC problems.

Update UILabel by timer doesn't work

I have UIView with Label that shows the time like stopwatch. I set the label from a Timer method. Here is the code below:
ivarTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/10.0
target:self
selector:#selector(updateTimer)
userInfo:nil
repeats:YES];
My update method looks like this:
- (void)updateTimer {
...
self.myLabel.text = #"someChangedStr";
}
It works great. But on the view I have UITableView and If I scroll the TableView, self.myLabel.text stops update. Should I use threads or something else?
You could run your timer on another thread, but you must always do UI updates on the main thread.
However, try adding your NSTimer to the UITrackingRunLoopMode so it will still fire when tracking touches on your window.
[[NSRunLoop mainRunLoop] addTimer:ivarTimer
forMode:UITrackingRunLoopMode];

Creating an iOS Timer

I am trying to create a "stop watch" type functionality. I have one label (to display the elapsed time) and two buttons (start and stop the timer). The start and stop buttons call the startTimer and stopTimer functions respectively. Every second the timer fires and calls the increaseTimerCount function. I also have an ivar timerCount which holds on to the elapsed time in seconds.
- (void)increaseTimerCount
{
timerCountLabel.text = [NSString stringWithFormat:#"%d", timerCount++];
}
- (IBAction)startTimer
{
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(increaseTimerCount) userInfo:nil repeats:YES];
}
- (IBAction)stopTimer
{
[timer invalidate];
[timer release];
}
The problem is that there seems to be a delay when the start button is pressed (which I am assuming is due to reinitializing the timer each time startTimer is called). Is there any way to just pause and resume the timer without invalidating it and recreating it? or a better/alternate way of doing this?
Thanks.
A bit dated but if someone is still interested...
don't "stop" the timer, but stop incrementing during pause, e.g.
- (void)increaseTimerCount
{
if (!self.paused){
timerCount++
}
timerCountLabel.text = [NSString stringWithFormat:#"%d", timerCount];
}
You can't pause the timer without using invalidate. What you can do is add
[timer fire];
after you create the timer in startTimer.