Can someone explain to me how exactly the NSTimer behavior is?
Basically I want to know if there is a way to always have the NSTimer event happen. Event if there is currently something executing.
For example, in:
NSTimer* testTimer = [NSTimer scheduledTimerWithTimeInterval:.01 target:self selector:#selector(testMethod) userInfo:nil repeats: NO];
for (int i=0; i<9999; i++) {
NSLog(#"%i", i);
}
testMethod will never be executed since the for loop is being executed when the event fires.
When the timer fires, it's selector is added to the run loop to be executed as soon as possible.
However, you're creating a loop so the run loop never gets a change to do the timer's selector resulting in what you're seeing - the app waits for the loop to finish before running your timer's selector.
If you have a long running task, it's best to put it into a new thread - try looking at performSelectorInBackground and reading up on threading in objective-c.
Related
I keep having troubles with my NSTimers and background selectors. It is driving me nuts and takes a very long time to try out each tweak. To preserve my sanity, and the sanity of future generations of cocoa programmers, I'm asking this question:
Is there an absolutely 100% sure way to have a scheduled, long-term timer fire at a later point in time, regardless of whether it was called from a background thread, main thread, etc?
It seems that I keep having to solve the same problem over and over again for the majority of my classes that use NSTimers. they work during short-term testing, let's say I set the timer to fire through a background thread to fire in 10 seconds. It works, because there's still a run loop running. But once I change the fire time to what I really want, like 15-30 minutes, there's dead silence. The run loop is gone and I don't know how to handle such a case. Nothing happens, and I discover such bugs a few days later, once I've already forgotten which timer would be responsible for that.
Currently I'm doing some really, really ugly dance with selectors, for example here's a test method(It seems to work for 10 minute timers):
//this is a test method to simulate a background task requesting a timer
[self performSelectorInBackground:#selector(backgroundReminderLongTermTest:) withObject:nil];
//this is a method similar to the one that the background thread would be trying to invoke
-(void)backgroundReminderLongTermTest:(id)sender
{
[self performSelectorOnMainThread:#selector(backgroundReminderFromMainThread:) withObject:nil waitUntilDone:NO];
}
//this is a wrapper for the background method, I want the timer to be added to a thread with a run loop already established and running
-(void)backgroundReminderFromMainThread:(id)sender
{
[playTimers addObject:[NSTimer scheduledTimerWithTimeInterval:1800 target:self selector:#selector(start:) userInfo:nil repeats:NO]];
}
I like the convenience of not having to worry about creating a fire date object with the scheduled timers, but should I just forget about them and use timers with specific fire dates? It seems that the scheduledTimer works well for short term tasks, when the run loop is already present, but I simply cannot see this kind of bugs during the app's execution. At one point, it seems that the timers are firing normally, but at a later point they stop firing completely.
Thank you for any help or clarification. I'm looking for a method that schedules timers without having to worry about whether or not a run loop is present every time I need to schedule a timer. I want to be sure that as long as the app is running, my timers, scheduled through this method would fire at predictable points in the future .
One of the myriad issues with NSTimers is their run-loop dependency. Every thread has a single run loop. If you schedule a timer on a background thread, it will be scheduled on that thread's run loop. If that thread is short lived, which background threads often are, that timer will quietly die with it.
The solution is to guarantee the timer is run on a thread that will be alive when the timer fires. The best way to do these dedicated background timers in my experience is to not use NSTimer at all, and go for GCD timers instead. Better men than I have coded up GCD powered timers. I personally prefer Mike Ash's article and implementation, which comes with an explanation.
Use local notification instead.
For as long as you depend on using scheduledTimerWithTimeInterval:... you cannot achieve what you want:
The timer will always be tied to the run-loop of the calling thread.
If there is no run-loop associated with that thread by the time of that message's invocation, there surely is one when the method returns as -[NSRunLoop currentRunLoop] creates a run-loop if necessary.
What you can do, if you don't like the other APIs for creation of a timer, is providing a category on NSTimer, which takes care of all the scheduling and so forth and that you can reuse in other projects.
Here is an example of what such a category might look like:
#pragma mark - setting up a timer:
+ (NSTimer *)yourPrefix_mainLoopScheduledTimerWithTimeInterval:(NSTimeInterval)interval target:(id)target selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)shouldRepeat
{
NSTimer *timer = [self yourPrefix_timerWithTimeInterval:interval target:target selector:selector userInfo:userInfo repeats:shouldRepeat];
void (^scheduler)() = ^{
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
};
if ([NSThread isMainThread]) {
scheduler();
} else {
// you should really be able to rely on the fact, that the timer is ready to roll, when this method returns
dispatch_sync(dispatch_get_main_queue(), scheduler);
}
return timer;
}
// this is just a convenience for the times where you actually want an _unscheduled_ timer
+ (NSTimer *)yourPrefix_timerWithTimeInterval:(NSTimeInterval)interval target:(id)target selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)shouldRepeat
{
NSDate *fireDate = [NSDate dateWithTimeIntervalSinceNow:interval];
NSTimer *timer = [[self alloc] initWithFireDate:fireDate interval:interval target:target selector:selector userInfo:userInfo repeats:shouldRepeat];
return [timer autorelease];
}
#pragma mark - tearing it down:
- (void)yourPrefix_invalidateMainLoopTimer
{
[self yourPrefix_invalidateMainLoopTimerAsynchronous:NO];
}
- (void)yourPrefix_invalidateMainLoopTimerAsynchronous:(BOOL)returnsImmediately
{
void (^invalidator)() = ^{
[self invalidate];
};
dispatch_queue_t mainQueue = dispatch_get_main_queue();
if (returnsImmediately) {
dispatch_async(mainQueue, invalidator);
return;
}
if (![NSThread isMainThread]) {
dispatch_sync(mainQueue, invalidator);
return;
}
invalidator();
}
Note the thread checks before using dispatch_sync because...
dispatch_sync
Discussion
[…] Calling this function and targeting the current queue results in deadlock.
(from The GCD Reference — emphasis mine)
Is considered thread-safe to call setFireDate: from another thread than the one in which the timer is scheduled? I mean, I detach this function in a new thread:
-(void)CFRunLoopTest {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
timer = [NSTimer timerWithTimeInterval:1 target:self selector:#selector(timerFireMethod:) userInfo:nil repeats:YES];
runLoop = CFRunLoopGetCurrent();
CFRunLoopAddTimer(runLoop, (CFRunLoopTimerRef)timer, kCFRunLoopCommonModes);
CFRunLoopRun();
[pool drain];
}
May I call [timer setFireDate:] from the main thread? I did not found anything in documentation that forbids it...
A note from the NSTimer reference for setFireDate: method says
You could potentially call this method
on a non-repeating timer that had not
yet fired, although you should always
do so from the thread to which the
timer is attached to avoid potential
race conditions.
Also see if the following
Discussion helps.
Why not run the timer on the main thread? I don't understand why you would need to run it in a separate thread. You could always have the timerFireMethod: spawn a new thread if it consumes a lot of time, Just run the appropriate method with performSelectorInBackground:withObject:.
EDIT: So the documentation actually says that it isn't thread safe to call[timer setFireDate:] from another thread. However, my advice is still valid.
I'm writing an application that has a timer functionality built in. Unfortunately, I'm having problems with the NSTimer and I'm not sure what I'm doing wrong. Here is where I'm declaring the timer...
if(!myTimer)
{
NSLog(#"Setting up the timer!");
myTimer=[NSTimer timerWithTimeInterval:1
target:self
selector:#selector(timerTicked)
userInfo:nil
repeats:YES];
}
Thanks to the NSLog function, I know the code to set the timer up is going off, but it isn't calling the function:
-(void)timerTicked:(NSTimer*)theTimer
{
//NSLOG that tells me that this function isn't being fired
}
Anyone have any idea what I'm doing wrong?
Your missing a trailing colon on your selector name. Should be something like this
selector:#selector(timerTicked:)
-- added after questioner comment
If it still doesn't work, check to make sure you are adding the timer to a run loop
[[NSRunLoop currentRunLoop] addTimer:myTimer forMode:NSDefaultRunLoopMode];
http://developer.apple.com/mac/library/documentation/cocoa/reference/foundation/Classes/NSTimer_Class/Reference/NSTimer.html#//apple_ref/doc/uid/20000319-CHDECCEE
See the Discussion segment of the docs, it talks about how to add the timer to the run loop and points to the run loop docs, too.
Im using alot of timers in my application. For recording time, moving object, fading etc. I use the same timer for several puposes in the same view at different times. How should I declare and invalidate or release my timers properly?
Atm Im declaring the timers like this:
fadeTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(bortInfo) userInfo:nil repeats:YES];
and as soon as im not using it im doing this:
[fadeTimer invalidate];
fadeTimer = nil;
The retain count when im leaving the view is 0 on every timer. Should i release the timer in the dealloc aswell? My app runs quite good, but from time to time it crashes.
The clockTimer that i use for updating a label with the time uses
[[NSRunLoop mainRunLoop] addTimer:clockTimer forMode:NSRunLoopCommonModes];
Do i need to do anything with this mainLoop once i invalidate the clockTimer?
All in all please support me with some info about working with timers.
Thank you very much!
Joakim
You're not retaining your timers properly - if you want to refer to them again you should retain them. I'd do this with a property i.e. in your header file
#property (nonatomic, retain) NSTimer *fadeTimer;
and change your code to say
self.fadeTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(bortInfo) userInfo:nil repeats:YES];
// Put this whenever you want to remove your timer and in your dealloc method.
[fadeTimer invalidate];
self.fadeTimer = nil;
This will make sure that your timer is retained by your object. Otherwise you just have to hope that the timer stays around and doesn't get autoreleased by the iPhone. And as you say it's crashing occasionally, this might be the reason ;)
I'm afraid I don't know much about run loop but am confused why your don't just use a normal NSTimer to schedule things - why bother interacting with the run loop at all?
Scheduled timers are retained by the run loop, and retain their target. If you want to retain the timer, you have to jump through a few hoops to prevent a retain cycle (I wrote a non-retaining proxy class, which is a bit messy but it works).
Don't manipulate the run loop unless you know what you're doing (I don't). A "scheduled" timer is already added to the main run loop. If you're generating clockTimer like fadeTimer, then it's being added to the run loop twice.
"from time to time it crashes" doesn't help anyone. Run it in the debugger and see where it crashes. It might even print some messages to the console if you're lucky.
*also you can use and this is a better and optimize way to write this line
if (theTimer != nil) {
if([theTimer isValid]){
[theTimer invalidate];
}
theTimer = nil;
}*
Can I send argument with #selector in NSTimer? If I want to release NSTimer, are the following steps right in dealloc?
[timer invalidate];
[timer release];
[timer release] only needs to be called if you "own" the timer. From Apple's documentation:
Because the run loop maintains the timer, from the perspective of memory management there's typically no need to keep a reference to a timer once you’ve scheduled it. Since the timer is passed as an argument when you specify its method as a selector, you can invalidate a repeating timer when appropriate within that method. In many situations, however, you also want the option of invalidating the timer—perhaps even before it starts. In this case, you do need to keep a reference to the timer, so that you can send it an invalidate message whenever is appropriate. If you create an unscheduled timer (see “Unscheduled Timers”), then you must maintain a strong reference to the timer (in a reference-counted environment, you retain it) so that it is not deallocated before you use it.
What does this mean?
If you alloc and init a timer, you must also release it, like so:
NSTimer * timer = [[NSTimer alloc] initWith...];
NSRunLoop * runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:timer forMode:NSDefaultRunLoopMode];
[timer release];
...
...
[timer invalidate];
timer = nil;
Once the timer has been added to the run loop, there is no reason to keep a reference to it anymore, since the run loops owns it. In this case, as shown, you would release the timer as soon as you add it to the run loop, and then simply invalidate it when you are finished. The final line (setting timer to nil) is for safety. The call to invalidate will result in the timer being released (by the run loop), so it is unsafe to keep a reference that points to it. Setting the local reference to nil keeps things kosher.
If, however, you create a timer using one of the convenience methods like so:
NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval ...];
You do not need to call [timer release] at all! The convenience method adds the timer to the run loop, which then owns it, so you do not need to perform any memory management on the returned timer object. You would simply invalidate the timer when you no longer want to use it:
[timer invalidate];
timer = nil;
Or, if the timer was not set to repeat, you would do absolutely nothing at all, since it would be released after its first invocation.
The two methods do different things. If you own a timer (you retained it, or alloced it, or copied it) then you should release it. If you scheduled it on a run loop, then you must invalidate it for the run loop to release it. If you did both things, then you must release and invalidate the timer (however usually having the run loop owning the timer is sufficient).
Always, release is the last thing you do. Once you release something there is no guarantee it is safe to dereference the object, which means it no longer safe to send it ANY message.
That is the correct way to deallocate a timer that might still be running (and you want to stop).