How to perform an action at a certain time with NSTimer? - ios

I'm trying to perform an action at a certain time of the day.
I don't want to use UILocalNotification as I don't want the users to be away if anything is happening.
My current code is:
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(fire:) userInfo:nil repeats:NO];

You could:
Extract the current local time each time your app starts using [NSDate date] in application:didFinishLaunchingWithOptions method.
Calculate the time left for your alarm time to occur using dateWithTimeInterval:sinceDate.
Start a NSTimer with interval = that_time_difference.
I'm not sure what do you mean by:
"I don't want the users to be away of anything happening."
I am assuming you don't want anything to happen if the app is not running. The only thing I can think of would be push notifications.

Related

Why a NSTimer can be delayed in background mode?

My app run in background and uses NSTimer to launch audio after a certain amount of time. Me and my testers have no problems with this, and audio can be launched event after several hours in the background (>10h).
My issue is that some users reports that the audio is often delayed, sometimes by a few minutes, sometimes by an hour.
I do something like that:
UIApplication * app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:nil];
dispatch_async(dispatch_get_main_queue(), ^{
theTimer = [NSTimer scheduledTimerWithTimeInterval:[theDateIWant timeIntervalSinceNow] target:self selector:#selector(playAudio) userInfo:nil repeats:NO];
});
My questions are :
-Does a NSTimer can be delayed that much by the system ? if so how to go around this problem ?
-How to reproduce this kind of issues ?
- Is it safer to use the following ?
theTimer = [[NSTimer alloc] initWithFireDate:theDateIWant interval:0.0 target:self selector:#selector(playAudio) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:theTimer
forMode:NSDefaultRunLoopMode];
Thanks for any help !
It seems to me that you understand background tasks a little bit wrong.
When the first snippet is performed (I assume that you place it in the applicationDidEnterBackground method), it says the system
I have a small task to perform, please wait for a while till i finish
it!
and the system will wait for about 5-10 minutes before suspend the application.
If you need kind of alarm, you can use LocalNotification to playback specific sound at specified time.

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

Why does my NSTimer double decrement my timer?

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...

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.

Two NSTimers with one governing the activity of the other?

I've been looking at the thread re: voice detection (http://mobileorchard.com/tutorial-detecting-when-a-user-blows-into-the-mic/).
I'm looking to implement something similar, however, with the following variation.
The levelTimer in that example fires continuously every .03 seconds. I essentially need to wrap another timer around this so that the voice check process only runs for (say) 10 seconds.
I'm finding that having something like:
outerTimer = [NSTimer scheduledTimerWithTimeInterval:10.0
target:self
selector:#selector(outerTimerFinished:)
userInfo:nil
repeats:NO];
levelTimer = [NSTimer scheduledTimerWithTimeInterval:0.03
target:self
selector:#selector(listenForSounds:)
userInfo:nil
repeats:YES];
…where outerTimerFinished method calls [levelTimer invalidate] isn't working - maybe due to thread blocking?
So can someone help me determine a way for levelTimer to do its work, but only for a specific amount of time?
Thanks.
Put a counter in the listenForSounds: method, and increment it each time the method is called. If you want to run the levelTimer for 10 seconds say, then check for when the counter > 333 and then invalidate the timer.

Resources