Why does my NSTimer double decrement my timer? - ios

I have been staring at my code for hours now so I thought I might try coming here for some fresh eyes. I needed to create a timer so I used the code below to do that. The first line is where I create the timer and the second part is my decrementTime method. This is in Objective C for an IOS app. This is my first time posting on StackOverflow (I usually find the answer I am looking for), so please let me know of any unwritten rules that I am not following.
_timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(decrementTime) userInfo:nil repeats:YES];
- (void)decrementTime{
self.timeLeft--;
}
I'll add here where I invalidate the first timer
-(IBAction)infoClick:(id)sender{
[_timer invalidate];
}
Then here is info message, where I create another timer
- (void)hideInfoMessage {
_secondTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(decrementTime) userInfo:nil repeats:YES];
}
clarification on my code: the order of events starts with my first code block (creating the timer). Then my second code block is called (invalidating the timer). then finally my third code block is called(making a new timer).
I know it is double incrementing because when I run the code I can visually see the timer double incrementing.

It's pretty easy to invoke the code that creates a timer twice. When you do that you actually have 2 timers running concurrently. Each one will decrement your value, so it will get decremented twice per second.
If you create a timer in your viewWillAppear method, for example, then you need to invalidate it in your viewWillDisappear method so you're sure you only have one running.
The same approach applies to other situations where you create a timer. You need to make sure you balance every call that creates a timer with a call that invalidates that timer.
If you use one of the scheduledTimer... methods, you can save a weak pointer to the timer. The run loop will retain it as long as it's running. When you invalidate it, the system run loop will release it and it will be deallocated. When that happens your weak pointer gets zeroed, so you don't even have to test it to see if it's valid/nil in your viewWillAppear method.
EDIT:
You need to instrument your code. In your infoClick method, is the variable _timer nil? What is it's address?
BTW, the target of an NSTimer is supposed to be a method that takes a single parameter, the timer itself. You should change your decrementTime method to look like this:
- (void) decrementTime: (NSTimer *) timer
{
NSLog(#"In method decrementTime, timer = %X", (unsigned long) timer)
self.timeLeft--;
}
Then look at your log and see if your decrementTime method is being called from 2 different timers (I would bet money that it is.)
You might also want to log the address of the timers you get back from your calls to scheduledTimerWithTimeInterval...

Related

How to properly handle NSTimer calls?

I am running into a situation where I'm not able to properly handle NSTimer.
In my app, I've an option of user chats (I'm not using XMPP because of a low budget project, but the chat is working through API calls ONLY). I've scheduled a timer at a time interval of 15 seconds. If any new chats available I'll get it and will update chat view.
Here's the working scenario:
As this is a UITabbar based app, a user will come to "Chat" tab.
A User will have a list of persons with whom he can chat.
A User will select any of a user – will push to Chat Screen.
Where all locally saved chats will be visible and an API call will be made for new chats, on success (or error) of API call, a timer will be scheduled to sync chats at a time interval of 15 seconds.
If a user goes back (pops), in viewDidDisappear: method, I'm invalidating the (running) timer.
In my Unit testing, if I'll continuously push & pop to/from Chat screen, there'll be multiple instances of that timer will get scheduled. I suspect, this is WRONG.
I'm not sure what I'm doing is correct or not though I need your help to understand the right and the correct way to get my functionality done. I guess here there's no need of the code for above explanation.
First of all, why are you not exploring option of push notification? Polling server every 15 second is a bad design :-(.
Second, when it comes to NSTimer it is important to start and stop them from the same thread. I would advise you encapsulate your timer start/stop code in below block always ensuring you deal on main thread with your timer.
dispatch_async(dispatch_get_main_queue(), ^{
});
This is the way o usually work with NSTimer. Hope it helps.
#implementation MainViewController{
NSTimer *myTimer
}
- (void)startTimer{
//Prevents multiple timers at the same time.
[myTimer invalidate];
myTimer = [NSTimer scheduledTimerWithTimeInterval:5.0f target:self selector:#selector(update) userInfo:nil repeats:YES];
}
- (void)update
{
//Stops the timer if the view in not on the screen
if (!(self.isViewLoaded && self.view.window)) {
[myTimer invalidate];
}
}
#end

objective c: memory management with NSTimer objects

I'm new in Objective C programming, I come from C++ and would better understand the ARC memory management; If I have the following situation:
-(void) test_method
{
NSTimer* t=[NSTimer ScheduledTimerWithTimeInterval:2
target:self selector;#selector(exec_method) userinfo:nil repeats:YES];
}
at the end of the method, I expected to lost the reference t, so, for the ARC, an automatic call to release and so deallocation of the NSTimer object, instead it seems that it still is in memory (the exec_method repeats its execution every 2 seconds)..or it will be deallocated when the system needs space in memory?
You do understand ARC correctly - this is just a slightly non-obvious case, because there is an a additional strong reference to your object that you cannot see. NSTimer is not behaving as expected because the fact that it is scheduled on the run loop means that it is retained there as well. So when your local goes away the object remains in memory.
ARC underlyingly uses a reference counting system - each object has a number (called the retain count) assigned to it, and only when that number reaches zero is the object released. When an object is created using alloc,copy,or new the retain count is set to 1. When the object is retained by another object the number increases, and when it is released it decreases (under the pre-ARC MRR system these were actual method calls made by the programmer - retain and release). ARC works in the same way, but just adds the same calls automatically at compile time).
So in this case the implicit call to release generated by ARC just reduces the count by 1 from 2 but since it does not reach zero the object is not released. Invalidating the timer will remove it from the runloop, and cause it to be deallocated.
From Apple docs:
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
maintain strong references to their timers, so you don’t have to
maintain your own strong reference to a timer after you have added it
to a run loop.
You must invalidate an NSTimer to remove it from the run loop.
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSTimer_Class/
In order to simplify this process, what you can do is create two methods, one to create and start the timer and one to invalidate the time. These methods would require you to declare your times as IVARs.
Swift:
let timer = NSTimer(timeInterval: 1.0, target: self, selector: "incrementCompletedUnitCount:",
userInfo: nil, repeats: true)
progress.cancellationHandler = {
timer.invalidate()
}
progress.cancel()
Objective-C
NSTimer * _studentTimer1;
-(void)startStudentTimer {
NSLog(#"***TIMER STARTED***");
_studentTimer1 = [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:#selector(dowork) userInfo:nil repeats:TRUE];
}
-(void)invalidateStudentTimer1 {
[_studentTimer1 invalidate];
}
Also, for safety, you may want to place your invalidation method inside the dealloc method of your view controller.
You may also consider extra safety measures by using a weak pointer to the timer like so:
NSTimer* __weak timer = [NSTimer scheduledTimerWithTimeInterval:30.0f target: self selector:#selector(tick) userInfo:nil repeats:YES];
or as an IVAR:
NSTimer * __weak _studentTimer1;
And no, as for your last question, the time will remain in the run loop until you explicitly invalidate it, that's why you need to be careful with NSTimer and should wrap it up in as much safety as possible.

Cancelling Parse retries on bad internet connection

I'm trying to do something quite simple : Stopping a parse.com query after a few seconds, with an NSTimer. I've read after some reasearch it's a good "trick" to use.
Here is how I create my timer :
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:6.0
target:self
selector:#selector(stopRetries:)
userInfo:#{#"query":query}
repeats:NO];
Because i'm running this on a background thread (and outside a viewcontroller class), the timer is inside a dispatch_sync(dispatch_get_main_queue());
But whatever I do, I cannot stop the query, because [query cancel] doesn't do anything. I can't pass it in the userInfo of the timer. Breakpoints show it has an address and is "there" but it looks like junk inside.
What can I be doing wrong and what should I be doing instead?
My main goal is to make the parse.com query stop faster than 30 seconds, and warn the user with an alert.
You can Try this [self performSelector:#selector(abc) withObject:nil afterDelay:6.0];
or
You can Invalidate the timer

scheduledTimerWithTimeInterval is not calling at correct intervals sometimes

I have two separate timers on for recording data at 0.25 seconds and other for recording location details at 1 sec as below
self.hardBrakingTimer = [NSTimer scheduledTimerWithTimeInterval:0.25
target:self
selector:#selector(timerFired:)
userInfo:nil
repeats:YES];
self.locationTimer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(locationTimerFired:)
userInfo:nil
repeats:YES];
But sometimes these timers are not calling at given time intervals.
You are scheduling a timer in a run loop in the default mode. A Run Loop is basically a queue, scheduling tasks on a certain thread. The exact execution time of your timer depends whether there are already other tasks executing in the same run loop.
It's also possible, that another run loop runs "on top" or modifies the "mode" such that other "tasks" are deferred (e.g a user scrolls). You can read more about run loops and "modes" and their surprising behavior in the official documentation: Anatomy of a Run Loop.
You are also likely not getting a precise timer with NSTimer anyway, since - as already pointed out by #Cyrille - there is that "Timer coalescing" feature of iOS and OSX: Timer Coalescing. You can however implement a quite precise timer using dispatch lib, see a code sample of mine: RXTimer

Call method on second - iOS

I have a method which displays a clock with seconds and the current time. This works fine except that this code will get called either half way through the current second or three quarters of the way through the current second depending on what time I open the app or run it. The method is called through the viewDidLoad method. When this happens my clock will be off up to almost 1 second. Is there any way to start my method when the next second start exactly? i.e. start it when the devices time is HH:MM:SS.000? Note: sorry if this is confusing with the excessive use of second and clock. I just mean I need to start my method at HH:MM:SS.000 (devices internal clock)
Using:
- (id)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)seconds
target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo
repeats:(BOOL)repeats
With an object of NSTimer is probably the way to go.
Add the logic found in this StackOverflow question/answers and you should be able to get it right on an exact second. (Use the logic there to create an NSDate object with resolution to 1 second, then use that date in the method I mentioned above).
NSTimer *yourTimer = [[NSTimer alloc] initWithFireDate:nowToTheSecond
interval:1.0 target:self selector:#selector(updateClock) userInfo:nil
repeats:YES];
[[NSRunLoop mainLoop] addTimer:yourTimer forMode:NSRunLoopCommonModes];
NSTimer objects are not exact. They depend on the app visiting the event loop frequently, and can vary by 50 MS or more (according to what I've read in the docs). If I remember correctly they try to "snap back" to the desired time interval rather than drifting, but any given firing will not be exact.
That said, I guess what I would do is to take the current NSDate, convert it to an NSTimeInterval, take the ceiling value (the next higher whole number) and start a one-time timer that will fire at that moment. Then in the handler for that timer, start a once-a-second timer. Something like this:
//Get the current date in seconds since there reference date.
NSTimeInterval nowInterval =[NSDate timeInervalSinceReferenceDate];
//Figure out the next even second time interval.
NSTimeInterval nextWholeSecond = ceil(nowInterval);
//Figure out the fractional time between now and the next even second
NSTimeInterval fractionUntilNextSecond = nextWholeSecond - nowInterval;
//Start a one-time timer that will go off at the next whole second.
NSTimer oneTimeTimer = [NSTimer timerWithTimeInterval: fractionUntilNextSecond
target: self
#selector: (startSecondTimer:)
userInfo: nil
repeats: NO];
And the startSecondTimer method:
- (void) startSecondTimer: (NSTimer *)timer;
{
//Start a new, repeating timer that fires once per second, on the second.
self.secondsTimer = [NSTimer timerWithTimeInterval: 1.0
target: self
#selector: (handleSecondTimer:)
userInfo: nil
repeats: YES];
}
You should still calculate the new time in each call to your handleSecondTimer: method rather than relying on the number of times you are called, because if the system gets really busy at the moment when it's supposed to call your timer and can't get to you, it might skip a call completely.
Disclaimer: I haven't tried this, but it should work. My only concern is edge cases. For example, when the next whole second is too close to now and the one-time timer can't fire fast enough. It might be safer to add a second to the fractionUntilNextSecond value, so the second hand doesn't start running for greeter than 1 second but less than 2 seconds.

Resources