How to get NSTimer to loop - objective-c

I'm trying to learn about NSTimer, using Foundation and printing to the console. Can anybody tell me what I need to do to get the following to work? It compiles with no errors, but does not activate my startTimer method -- nothing prints.
My aim is to get one method to call another method to run some statements, and then stop after a set time.
#import <Foundation/Foundation.h>
#interface MyTime : NSObject {
NSTimer *timer;
}
- (void)startTimer;
#end
#implementation MyTime
- (void)dealloc {
[timer invalidate];
[super dealloc];
}
- (void)startTimer {
timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(runTimer:) userInfo:nil repeats:YES];
}
- (void)runTimer:(NSTimer *)aTimer {
NSLog(#"timer fired");
}
#end
int main(int argc, char *argv[]) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
MyTime *timerTest = [[MyTime alloc] init];
[timerTest startTimer];
[timerTest release];
[pool release];
return 0;
}

The timer never gets a chance to fire in your program, because the program ends almost immediately after the timer is created.
There's a construct called the Run Loop which is responsible for processing input, including input from timers. One run loop is created for each thread, but it isn't automatically started in this case.
You need to run the run loop and keep it going until the timer has a chance to fire. Fortunately, this is quite easy. Insert:
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5.0]];
between sending startTimer and release to timerTest. If you want the timer to repeat, you'll need to continue keeping the run loop active.
Note that you only need to do that in a simple program like this; when you are creating an application with a GUI, the run loop will be started via the Cocoa application setup process, and will remain active until the application terminates.

You have to add your timer to the default runloop after initializing it:
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
Put this in -(void)startTimer.

Related

Objective-C: [NSObject performSelector: onThread...] does not get called if the thread is not the main one

Very similar issue is already discussed here. The problem at hand and what I am trying to achieve is to call a function on a given object in the thread it is created at. Here is the complete case:
an instance of class A is created in a given NSThread (Thread A) (not the main one). The instance keeps its creating NSThread as a member variable.
an instance of class B has one of its member functions executing in another NSThread - Thread B, and wants to call a function of A in A's creation thread. Thus B's currently executing function issues the following call:
[_a performSelector: #(fun)
onThread: _a.creationThread
withObject: nil
waitUntilDone: NO];
If the creation thread of A's instance is not the main one, fun never gets called. If the creation thread is the main one it is always called. First I was thinking whether the thread that created A's instance has been destroyed and the pointer points to an invalid thread but actually calling any functions on the thread object (Thread A) produces valid results and no crashes. Also checking the object is valid according to this check. Any suggestions?
Update:
What I'm doing is creating a timer on a background thread:
_timer = [NSTimer timerWithTimeInterval:60.0 target:self selector:#selector(fun:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSDefaultRunLoopMode];
This code is the one starting the timer. The timer is not started in any specific background thread. It just happens that the function that creates the timer could be called in any thread. Thus the timer should be invalidated in the exactly same one as the NSTimer documentation states: "you should always call the invalidate method from the same thread on which the timer was installed."
To run timer on background thread, you have two options.
Use dispatch timer source:
#property (nonatomic, strong) dispatch_source_t timer;
and you can then configure this timer to fire every two seconds:
- (void)startTimer {
dispatch_queue_t queue = dispatch_queue_create("com.domain.app.timer", 0);
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(self.timer, dispatch_walltime(NULL, 0), 2.0 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
dispatch_source_set_event_handler(self.timer, ^{
// call whatever you want here
});
dispatch_resume(self.timer);
}
- (void)stopTimer {
dispatch_cancel(self.timer);
self.timer = nil;
}
Run NSTimer on background thread. To do this, you can do something like:
#property (atomic) BOOL shouldKeepRunning;
#property (nonatomic, strong) NSThread *timerThread;
#property (nonatomic, strong) NSTimer *timer;
And
- (void)startTimerThread {
self.timerThread = [[NSThread alloc] initWithTarget:self selector:#selector(startTimer:) object:nil];
[self.timerThread start];
}
- (void)stopTimerThread {
[self performSelector:#selector(stopTimer:) onThread:self.timerThread withObject:nil waitUntilDone:false];
}
- (void)startTimer:(id)__unused object {
#autoreleasepool {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:#selector(handleTimer:) userInfo:nil repeats:YES];
[runLoop addTimer:self.timer forMode:NSDefaultRunLoopMode];
self.shouldKeepRunning = YES;
while (self.shouldKeepRunning && [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]])
;
self.timerThread = nil;
}
}
- (void)handleTimer:(NSTimer *)timer {
NSLog(#"tick");
}
- (void)stopTimer:(id)__unused object {
[self.timer invalidate];
self.timer = nil;
self.shouldKeepRunning = FALSE;
}
I'm not crazy about the shouldKeepRunning state variable, but if you look at the Apple documentation for the run method, they discourage the reliance upon adding sources/timers to run loops:
If you want the run loop to terminate, you shouldn't use this method. Instead, use one of the other run methods and also check other arbitrary conditions of your own, in a loop. A simple example would be:
BOOL shouldKeepRunning = YES; // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
Personally, I'd recommend the dispatch timer approach.

NSTimer with CFRunLoop

I am having a strange issue. My NSTimer CallMe method does not get fired without CFRunLoop. And if I add CFRunLoop then the line after CFRunLoop never gets called. I want both the line after CFRunLoop and CallMe method to get fired.
EDIT - I dont care about CFRunLoop just want the the timer to fire every 20 secs.
I have added the code and the comments below
int main(int argc, char * const argv[]) {
Updater *theUpdater = [[Updater alloc] init];
NSTimer *theTimer = [theUpdater getTimer];
[theTimer retain];
//CFRunLoopRun();
NSLog(#"I am here"); //If I uncomment CFRUnLoop this line does not get executed
}
#implementation Updater
- (NSTimer *)getTimer {
NSLog(#"Timer started");
NSTimer *theTimer;
theTimer = [NSTimer scheduledTimerWithTimeInterval:20.0
target:self
selector:#selector(CallMe:)
userInfo:nil
repeats:YES];
return theTimer;
}
-(void) CallMe:(NSTimer*)theTimer{
NSLog(#"I Never get called");
}
#end
Your timer needs to be created in the context of your CFRunLoop. So your code should look more like this:
NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
Updater * theUpdater = [[Updater alloc] init];
[myRunLoop run]
You should have the Updater object retain your timer, since it is its owner. Note that when you invoke the run function that it will never terminate. For other options consult the NSRunLoop documentation.
This is the expected behavior. From the CFRunLoop docs (emphasis added):
CFRunLoopRun
Runs the current thread’s CFRunLoop object in its default mode
indefinitely.
In other words, CFRunLoopRun() never returns, unless you call CFRunLoopStop() somewhere.

Objective-C: Blocking a Thread until an NSTimer has completed (iOS)

I've been searching for and attempting to program for myself, an answer to this question.
I've got a secondary thread running inside my mainView controller which is then running a timer which counts down to 0.
Whilst this timer is running the secondary thread which initiated the timer should be paused/blocked whatever.
When the timer reaches 0 the secondary thread should continue.
I've Experimented with both NSCondition and NSConditionLock with no avail, so id ideally like solutions that solve my problem with code, or point me to a guide on how to solve this. Not ones that simply state "Use X".
- (void)bettingInit {
bettingThread = [[NSThread alloc] initWithTarget:self selector:#selector(betting) object:nil];
[bettingThread start];
}
- (void)betting {
NSLog(#"betting Started");
for (int x = 0; x < [dealerNormalise count]; x++){
NSNumber *currSeat = [dealerNormalise objectAtIndex:x];
int currSeatint = [currSeat intValue];
NSString *currPlayerAction = [self getSeatInfo:currSeatint objectName:#"PlayerAction"];
if (currPlayerAction != #"FOLD"){
if (currPlayerAction == #"NULL"){
[inactivitySeconds removeAllObjects];
NSNumber *inactivitySecondsNumber = [NSNumber numberWithInt:10];
runLoop = [NSRunLoop currentRunLoop];
betLooper = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(betLoop) userInfo:nil repeats:YES];
[runLoop addTimer:[betLooper retain] forMode:NSDefaultRunLoopMode];
[runLoop run];
// This Thread needs to pause here, and wait for some input from the other thread, then continue on through the for loop
NSLog(#"Test");
}
}
}
}
- (void)threadKiller {
[betLooper invalidate];
//The input telling the thread to continue can alternatively come from here
return;
}
- (void)betLoop {
NSLog(#"BetLoop Started");
NSNumber *currentSeconds = [inactivitySeconds objectAtIndex:0];
int currentSecondsint = [currentSeconds intValue];
int newSecondsint = currentSecondsint - 1;
NSNumber *newSeconds = [NSNumber numberWithInt:newSecondsint];
[inactivitySeconds replaceObjectAtIndex:0 withObject:newSeconds];
inacTimer.text = [NSString stringWithFormat:#"Time: %d",newSecondsint];
if (newSecondsint == 0){
[self performSelector:#selector(threadKiller) onThread:bettingThread withObject:nil waitUntilDone:NO];
// The input going to the thread to continue should ideally come from here, or within the threadKiller void above
}
}
You can't run a timer on a thread and sleep the thread at the same time. You may want to reconsider whether you need a thread at all.
There's a few things that need to be pointed out here. First, when you schedule your timer:
betLooper = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:#selector(betLoop:)
userInfo:nil
repeats:YES];
it's added to and retained by the current run loop by that method, so you don't need to do that manually. Just [myRunLoop run]. Your timer's selector argument is also invalid -- a timer's "target method" needs to look like this:
- (void)timerFireMethod:(NSTimer *)tim;
This also means that you don't need to retain the timer if all you want to do is invalidate it, since you will have a reference to it from inside that method.
Second, it's not clear what you mean by "this thread needs to sleep to wait for input". When you schedule that timer, the method (betLoop) is called on the same thread. If you were to sleep the thread, the timer would stop too.
You seem to be a little mixed up regarding methods/threads. The method betting is running on your thread. It is not itself a thread, and it's possible to call other methods from betting that will also be on that thread. If you want a method to wait until another method has completed, you simply call the second method inside the first:
- (void)doSomethingThenWaitForAnotherMethodBeforeDoingOtherStuff {
// Do stuff...
[self methodWhichINeedToWaitFor];
// Continue...
}
I think you just want to let betting return; the run loop will keep the thread running, and as I said, the other methods you call from methods on the thread are also on the thread. Then, when you've done the countdown, call another method to do whatever work needs to be done (you can also invalidate the timer inside betLoop:), and finalize the thread:
- (void)takeCareOfBusiness {
// Do the things you were going to do in `betting`
// Make sure the run loop stops; invalidating the timer doesn't guarantee this
CFRunLoopStop(CFRunLoopGetCurrent());
return; // Thread ends now because it's not doing anything.
}
Finally, since the timer's method is on the same thread, you don't need to use performSelector:onThread:...; just call the method normally.
You should take a look at the Threading Programming Guide.
Also, don't forget to release the bettingThread object that you created.
NSThread has a class method + (void)sleepForTimeInterval:(NSTimeInterval)ti. Have a look at this :).
NSThread Class Reference

Objective-C Threading: Exiting a thread, retain problems

I'm trying to create a thread that configures a run loop to run a physics engine through a defined NSTimer. However, I'm having trouble making the thread exit normally (or I think the problem is).
Attached are the relevant portions of my code:
(This code is in a view controller)
(back is called when a button is pressed)
- (void)back {
[timestep invalidate];
exiting = YES;
[self release];
}
- (void)initializePhysicsWorld {
// Initializes the thread to simulate physics interactions.
[NSThread detachNewThreadSelector:#selector(physicsThreadMethod)
toTarget:self
withObject:nil];
}
- (void)physicsThreadMethod {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSRunLoop *myRunLoop = [NSRunLoop currentRunLoop];
timestep = [NSTimer timerWithTimeInterval:1.0f/60.0f
target:self
selector:#selector(step:)
userInfo:nil
repeats:YES];
[myRunLoop addTimer:timestep forMode:NSDefaultRunLoopMode];
while (!exiting) {
CFRunLoopRun();
[pool release];
pool = [[NSAutoreleasePool alloc] init]; // periodically refreshes pool
}
CFRunLoopStop([myRunLoop getCFRunLoop]);
NSLog(#"Thread is going to exit");
[pool release];
}
- (void)dealloc {
if ([self.view superview]) {
[self.view removeFromSuperview];
}
[super dealloc];
}
The engine (the step: function) runs fine, but when I try to exit the loop by running the method back, it would appear that the thread does not release its retain on my view controller (dealloc is not called). I think my thread didn't exit the physicsThreadMethod method as the NSLog does not appear in the console. Dealloc was only called when I run 'back' a second time.
I'm not really sure why this is happening, so I would really appreciate any help. Thanks!
The problem lies in here:
while (!exiting) {
CFRunLoopRun(); //<-- here you start the run loop.
// the lines under this line are NEVER executed. also the while loop does nothing
[pool release];
pool = [[NSAutoreleasePool alloc] init]; // periodically refreshes pool
}
You could use NSOperations to wrap your work, there are already properties defined on this class to do exactly this kind of thing.
If you want to stick with your implementation in a NSThread you have to take a look at the CFRunLoop reference and how to add observers to the run loop.
Does CFRunLoopRun return every step:? CFRunLoopStop or [myRunLoop runUntilDate:] would help.

Objective C: App freezes when using a timer

It took me hours to figure out how to implement a timer into my program, but when it runs, the app doesn't load completely as it did before the timer.
In my main.m:
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
OutLauncher *theLauncher = [[OutLauncher alloc] init];
NSTimer *theTimer = [theLauncher getTimer];
[theTimer retain];
[[NSRunLoop currentRunLoop] addTimer: theTimer forMode: NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
[pool release];
return 0;
}
The file OutLauncher is being imported into that, which looks like this:
- (void)doStuff {
NSLog( #"Doing Stuff");
}
- (NSTimer *)getTimer{
NSTimer *theTimer;
theTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector: #selector(doStuff) userInfo:nil repeats:YES];
return [theTimer autorelease];
}
The timer works, the console updates every second with the phrase "doing stuff" but the rest of the program just won't load. It will if I comment out the code I added to int main though
A few things:
You don't need to autorelease the timer you return after setting one up with [NSTimer scheduledTimerWithTimeInterval:] It is already autoreleased.
The timer created via scheduledTimerWithInterval is already added to the default run loop. So you don't need to use the following:
[[NSRunLoop currentRunLoop] addTimer: theTimer forMode: NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
In fact, you don't even need to keep a reference to the timer unless you need to cancel it yourself.
Here is what apple has to say about what you are doing in the documentation
run
Puts the receiver into a permanent
loop, during which time it processes
data from all attached input sources.
(void)run Discussion If no input sources or timers are attached to the
run loop, this method exits
immediately; otherwise, it runs the
receiver in the NSDefaultRunLoopMode
by repeatedly invoking
runMode:beforeDate:. In other words,
this method effectively begins an
infinite loop that processes data from
the run loop’s input sources and
timers.
Manually removing all known input
sources and timers from the run loop
is not a guarantee that the run loop
will exit. Mac OS X can install and
remove additional input sources as
needed to process requests targeted at
the receiver’s thread. Those sources
could therefore prevent the run loop
from exiting.
If you want the run loop to terminate,
you shouldn't use this method.
Instead, use one of the other run
methods and also check other arbitrary
conditions of your own, in a loop. A
simple example would be:
BOOL shouldKeepRunning = YES;
// global NSRunLoop *theRL =
[NSRunLoop currentRunLoop]; while
(shouldKeepRunning && [theRL
runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]]);
where shouldKeepRunning is set to NO
somewhere else in the program.
Availability Available in iOS 2.0 and
later.
So it looks like your code is doing what it is supposed to do. It is Logging all the timer events and waiting indefinitely for the run loop.
It looks like you're making it a lot more complicated than it needs to be. You don't need to put any code in your main.m file. If you want to fire the doStuff method every second, this is all the code you need:
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector: #selector(doStuff) userInfo:nil repeats:YES];
You don't need to (auto)release it yourself. timer is already autoreleased. If you want to be able to cancel the timer, you will need to keep a reference of it. Then when you want to cancel, you just call invalidate and set the reference to nil.

Categories