Fire a NSThread every 'X' seconds - objective-c

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

Related

Pause and resume Timer in ViewController

I want to pause and resume timer in viewcontroller which is displayed as subview in mainviewcontroller. Play/Pause button in mainviewcontroller when pressed it displays view controller as subview as well as plays audio.
Have this NSTimer in view controller
[NSTimer scheduledTimerWithTimeInterval:2.6
target:self
selector:#selector(updateView:)
userInfo:nil
repeats:YES];
- (void)updateView:(NSTimer *)theTimer
{
if (index < [textArray count])
{
self.textView.text = [self.textArray objectAtIndex:index];
self.imageView.image = [self.imagesArray objectAtIndex:index];
index++;
} else {
index = 0;
}
I want to pause and resume timer when play/pause button is pressed in mainviewcontroller. How can i do that.
NSTimer* myTimer = [NSTimer scheduledTimerWithTimeInterval:2.6
target:self
selector:#selector(updateView:)
userInfo:nil
repeats:YES];
[myTimer invalidate];
/*
Stops the receiver from ever firing again and requests its removal from its run loop.
*/
[myTimer fire];
/*
You can use this method to fire a repeating timer without interrupting its regular firing schedule. If the timer is non-repeating, it is automatically invalidated after firing, even if its scheduled fire date has not arrived.
*/
First you have to keep a reference of your timer in your interface-file:
NSTimer *myTimer;
In the implementation file you can simply connect and then refer to it:
myTimer = [NSTimer scheduledTimerWithTimeInterval:2.6
target:self
selector:#selector(updateView:)
userInfo:nil
repeats:YES];
To stop the timer call:
[myTimer invalidate];
To start it again:
[myTimer fire];
See NSTimer Class Reference for more info about the NSTimer Class.

How to stop NSTimer started on button click in this timer callback method

I am having problems with stoping NSTimer started on button click. [timer invalidate] and timer = nil; just do nothing neither when I am trying to stop it viewWillDisappear nor in method invoked by method which is being invoked by this timer. However when I start my timer in viewWillAppear and invalidate it in viewWillDisappear everything is fine.
I suppose there might be an issue in thread I am starting the timer from. Can you help with that?
I looked through all answers here regarding NSTimer not stopping, but they didn't help to solve the problem.
The way I initialize my timer:
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(oneSecondPassedSinceRoundStarted) userInfo:nil repeats:YES];
The ways I tried to stop it:
[self.timer invalidate];
[timer invalidate];
It's funny how quick you can answer your own question after you asked for a help. No matter how long you have been struggling to find the answer before by yourself:
[self performSelectorOnMainThread:#selector(stopTimer) withObject:nil waitUntilDone:YES];
And the selector:
- (void) stopTimer
{
[timer invalidate];
timer = nil;
}
I have created and tested this:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
myTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(oneSecondPassedSinceRoundStarted:) userInfo:nil repeats:YES];
}
-(void)oneSecondPassedSinceRoundStarted:(NSTimer *)time {
// Do what You want
NSLog(#"CALLING!");
}
-(IBAction)buttonAction:(id)sender {
NSLog(#"Stop timer!");
[myTimer invalidate];
myTimer = nil;
}
And it is working nice.
Note: You forget to add colon after oneSecondPassedSinceRoundStarted.

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

NSTimer doesn't call method

I'm really frustrated now, googled the whole internet, stumbled through SO and still didn't find a solution.
I'm trying to implement an NSTimer, but the method which I defined doesn't get called. (seconds are set correctly, checked it with breakpoints). Here is the code:
- (void) setTimerForAlarm:(Alarm *)alarm {
NSTimeInterval seconds = [[alarm alarmDate] timeIntervalSinceNow];
theTimer = [NSTimer timerWithTimeInterval:seconds
target:self
selector:#selector(showAlarm:)
userInfo:alarm repeats:NO];
}
- (void) showAlarm:(Alarm *)alarm {
NSLog(#"Alarm: %#", [alarm alarmText]);
}
The object "theTimer" is deined with #property:
#interface FooAppDelegate : NSObject <NSApplicationDelegate, NSWindowDelegate> {
#private
NSTimer *theTimer;
}
#property (nonatomic, retain) NSTimer *theTimer;
- (void) setTimerForAlarm:(Alarm *)alarm;
- (void) showAlarm:(Alarm *)alarm;
What am I doing wrong?
timerWithTimeInterval simply creates a timer, but doesn't add it to any run loops for execution. Try
self.theTimer = [NSTimer scheduledTimerWithTimeInterval:seconds
target:self
selector:#selector(showAlarm:)
userInfo:alarm repeats:NO];
instead.
Also don't forget to check if
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds
target:(id)target
selector:(SEL)aSelector
userInfo:(id)userInfo
repeats:(BOOL)repeats
is called in the main thread.
You've created an NSTimer object but you haven't scheduled it to be run. timerWithTimeInterval:target:selector:userInfo:repeats: creates a timer that you can schedule to run later, for example, to create a timer at application launch and have it start counting when the user presses a button. Either call
[[NSRunLoop currentRunLoop] addTimer:theTimer forMode:NSDefaultRunLoopMode]
at the end of setTimerForAlarm or replace
theTimer = [NSTimer timerWithTimeInterval:seconds
target:self
selector:#selector(showAlarm:)
userInfo:alarm repeats:NO];
with
theTimer = [NSTimer scheduledTimerWithTimeInterval:seconds
target:self
selector:#selector(showAlarm:)
userInfo:alarm repeats:NO];
which creates a timer and immediately schedules it.
Well you may want to actually schedule your NSTimer on the run loop :) instead of timerWithTimeInterval use scheduledTimerWithTimeInterval.
theTimer = [NSTimer scheduledTimerWithTimeInterval:seconds
target:self
selector:#selector(showAlarm:)
userInfo:alarm repeats:NO];
While all of the answers are right, there is an even simpler solution that doesn't involve a NSTimer at all. Your setTimerForAlarm: implementation can be reduced to one simple line:
[self performSelector:#selector(showAlarm:) withObject:alarm afterDelay:[[alarm alarmDate] timeIntervalSinceNow]]

Cocoa Touch - Timers

How can I make a timer that counts down from 3 and then runs a method? How would I do that?
Is that different from a timer counting from 0 to 3? It will still wait three seconds, either way.
[NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:#selector(myMethod:) userInfo:nil repeats:NO];
Better way might be to use performSelector:withObject:afterDelay: method:
[self performSelector:#selector(myMethod) withObject:nil afterDelay:3.0f];
Or in case method takes 1 parameter:
[self performSelector:#selector(myMethod:) withObject:parameter afterDelay:3.0f];
If method takes multiple parameters you'll need to use NSInvocation class
- (void) handleTimer: (NSTimer *) timer
{
do some work here...
} // handleTimer
// at some point in your controller
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval: 3.0
target: self
selector: #selector(handleTimer:)
userInfo: nil
repeats: NO];