NSTimer's action method is not being called - objective-c

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;
}

Related

NSTimer Not Stopping When Invalidated

I have the following code in my .h file:
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#import <AVFoundation/AVFoundation.h>
#import <MapKit/MapKit.h>
#interface LandingController : UIViewController<CLLocationManagerDelegate> {
CLLocationManager *LocationManager;
AVAudioPlayer *audioPlayer;
}
#property (nonatomic, retain) NSTimer *messageTimer;
- (IBAction)LoginButton:(id)sender;
#end
I have the following code in my .m file:
#interface LandingController ()
#end
#implementation LandingController
#synthesize messageTimer;
- (void)checkForMessages
{
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:#"BINGO:"
message:#"Bingo This Works"
delegate:nil
cancelButtonTitle:#"Okay"
otherButtonTitles:nil];
[alert show];
}
- (IBAction)LoginButton:(id)sender {
if ([UserType isEqualToString:#"Owner"]) {
if (messageTimer){
[self.messageTimer invalidate];
self.messageTimer = nil;
}
} else {
if (!messageTimer){
self.messageTimer = [NSTimer scheduledTimerWithTimeInterval:10.0
target:self
selector:#selector(checkForMessages)
userInfo:nil
repeats:YES];
}
}
}
#end
But my timer doesn't want to stop when I call the invalidate.
The LoginButton is only pressed twice, once when the strResult is = to "Guard" and then the application changes it to be equal to "Owner" and the user presses the login button again, so I don't think I'm setting multiple timers.
After pressing the login button and starting the timer I segue to another view and then segue back to press the login button once more which is when I want the timer to stop. Do I need to do anything special to get the messageTimer since I switched views for a moment and then came back?
Any ideas?
Thanks!
You need to call [self.messageTimer invalidate] on the same thread on which you created the timer. Just make sure that the timer is created and invalidated on main thread.
dispatch_async(dispatch_get_main_queue(), ^{
if ([UserType isEqualToString:#"Owner"]) {
[self.messageTimer invalidate];
self.messageTimer = nil;
} else {
self.messageTimer = [NSTimer scheduledTimerWithTimeInterval:10.0
target:self
selector:#selector(checkForMessages)
userInfo:nil
repeats:YES];
}
});
NSTimer is retained by NSRunLoop, so the only way I see your issue happening is if you're actually creating more than one timer and invalidating only what you have reference to.
Example:
if(!timer)
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:(self) selector:#selector(processTimer) userInfo:nil repeats:YES];
Have you try to put repeat as No
self.messageTimer = [NSTimer scheduledTimerWithTimeInterval:10.0
target:self
selector:#selector(checkForMessages)
userInfo:nil
repeats:NO];
If the code at the end (starting with if) is called twice with UserType != Owner, you create a new timer without invalidating the old one. Now two timers are running. If the code executes a third time, you will add a third timer and so on. Executing the code with UserType == Owner only invalidates the last timer and even it is called repeatly, it does not invalidate older timers.
You have a timer leak. ;-)
How about put an NSLog in your checkForMessages method? It would be easier to check if there's really only 1 timer.
(I'd rather put this in a comment, but I don't have that much reputation....)
I have an approach for stopping or deactivate the timer, Before apply this make sure that you tried all the cases as mentioned above so you can also understand that why this approach used at last.
// Instead of self use NSTimer class, it will not provide you any crash and in selector placed any empty function and setRepeats to NO
self.messageTimer = [NSTimer scheduledTimerWithTimeInterval:100.0
target:NSTimer selector:#selector(emptyFunctionCalling)
userInfo:nil
repeats:NO];
[self.messageTimer invalidate];
self.messageTimer = nil;
So whenever the case occured that timer will not stopping then entering in this code the timer will stops permanently.
Its weird but invalidating passed timer reference and created timer reference worked for me.
delayTimer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(updateDelayLable:) userInfo:nil repeats:YES];
-(void)updateSendDataDelayLable:(NSTimer*)timer{
delayValueForGNSS--;
if (delayValueForGNSSSend==0) {
[timer invalidate];
timer = nil;
[delayTimer invalidate];
delayTimer = nil;
}
}

Fire a NSThread every 'X' seconds

I'm having an issue with NSThread which I don't understand very well..
How to well create a NSThread:
- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument
then...
I'm a bit confuse with NSThread and all of his methods.
I want to create a NSThread and fire it every 5 minutes (and of course keep using my applications without latence :)
Or use a dispatch source with GCD since Apple is recommending migrating away from NSThread use.
Assuming the following ivar exists:
dispatch_source_t _timer;
then, for example:
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, backgroundQueue);
dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC, 0.05 * NSEC_PER_SEC);
dispatch_source_set_event_handler(_timer, ^{
NSLog(#"periodic task");
});
dispatch_resume(_timer);
which will fire a small task on a background queue every 2 seconds with a small leeway.
You could set an NSTimer up that will run a method that starts your thread
// Put in a method somewhere that i will get called and set up.
[NSTimer timerWithTimeInterval:10 target:self selector:#selector(myThreadMethod) userInfo:nil repeats:YES];
or
[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(myThreadMethod) userInfo:nil repeats:YES];
You can also set this to an NSTimer so you can set up the poroperties of the timer. Such as start and finish.
- (void)myThreadMethod
{
[NSThread detachNewThreadSelector:#selector(someMethod) toTarget:self withObject:nil];
}
You can try to use a NSTimer to implement it. Register a NSTimer in your main thread:
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:300 target:self selector:#selector(doSomething) userInfo:nil repeats:YES];
and you can have -doSomething to start a thread to do your actual work:
-(void) doSomething {
dispatch_queue_t doThings = dispatch_queue_create("doThings", NULL);
dispatch_async(doThings, ^{
//Do heavy work here...
dispatch_async(dispatch_get_main_queue(), ^{
//Here is main thread. You may want to do UI affair or invalidate the timer here.
});
});
}
You can refer to NSTimer Class and GCD for more info.
I suggest NSTimer + NSThred for your purpose
[NSTimer scheduledTimerWithTimeInterval:300 target:self selector:#selector(triggerTimer:)
userInfo:nil repeats:YES];
-(void) triggerTimer:(NSTimer *)theTimer
{
//Here perform the thread operations
[NSThread detachNewThreadSelector:#selector(myThreadMethod) toTarget:self withObject:nil];
}

NSTimer supposed to be fired from inside a method called by a notification

In my iOS application I have registered the AppDelegate as notification listener for CoreData changes. With this piece of code:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(mergeChangesFromContextDidSaveNotification:)
name:NSPersistentStoreDidImportUbiquitousContentChangesNotification
object:[self persistentStoreCoordinator]];
And the mergeChangesFromContextDidSaveNotification method get correctly called every time there's an update.
However inside this method I am trying to call an NSTimer for doing another operation:
- (void)mergeChangesFromContextDidSaveNotification:(NSNotification *)notification {
NSTimer *t =[NSTimer scheduledTimerWithTimeInterval:10.0
target:self
selector:#selector(mergeCoreDataFromCloud:)
userInfo:nil
repeats:NO];
}
}
and the point is that mergeCoreDataFromCloud: which should be fired by the timer is never called. This is the signature:
-(void)mergeCoreDataFromCloud:(NSTimer*)timer {
// never called...
}
please note that I am at early stage of development, the code is not perfect, and I am only interested in knowing why the timer is not started.
I suppose it has something to do with threads, but I have no guess...
thanks
A timer relies on the run loop being run in the mode(s) in which it has been scheduled.
In a Cocoa app, the main thread runs its run loop automatically. However, no other thread can be relied on to run its run loop automatically.
So, usually you want to schedule timers on the main thread. You can also schedule them on a thread you create and control, which you cause to run its run loop in an appropriate mode.
There's no enough information in your question to know what thread this code is being called on.
You could be right about the thread issue -- it that's the case, then I think you have to use an unscheduled timer and add it to the run loop manually. Try this, and see if it works:
NSTimer *t = [NSTimer timerWithTimeInterval:10 target:self selector:#selector(mergeCoreDataFromCloud:) userInfo:nil repeats:NO];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:t forMode:NSDefaultRunLoopMode];

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.

NSTimer not firing

I have an NSTimer that I init with this code:
testTimer = [[NSTimer alloc] initWithFireDate:[new objectAtIndex:0] interval:0.0 target:self selector:#selector(works:) userInfo:nil repeats:NO];
[new objectAtIndex:0] is an NSDate in the past.
When I start up the app, the timer is getting created, with a fireDate of immediately (since the date is in the past), however it never calls my works method. (-(void)works:(id)sender
)
Anybody know why this is happening?
You will have to add it to the current run loop if you use initWith.. method to create the timer object.
NSRunLoop * theRunLoop = [NSRunLoop currentRunLoop];
[theRunLoop addTimer:testTimer forMode:NSDefaultRunLoopMode];
Or if you would like it set up for you, use the scheduled... methods to create your timer.
I just recently had an issue with NSTimer. In my case I didn't realize that the method scheduledTimerWithTimeInterval is not multi thread safe. Once I moved the timer to the main thread it started working.
I think I had the same problem as Dobler, but my solution was different.
The problem was that the timer was being created and scheduled in a GCD thread in a block within a
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{})
call (actually nested deep, so it wasn't obvious that this was the case).
Using NSTimer's scheduledTimerWithTimeInterval:... placed the timer into an invalid run loop.
The fix was to change to
timer = [NSTimer timerWithTimeInterval:1.0f target:self selector:#selector(...) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];