NStimer -- what am I doing wrong here? - iphone

I've been using an NSTimer successfully, but am now having trouble with it. Undoubtably something stupid. Appreciate another set of eyes. Running the debugger, I see that applicationDidFinishLaunching is called, but trigger is never called.
-(void) trigger:(NSTimer *) theTimer{
NSLog(#"timer fired");
}
- (void)applicationDidFinishLaunching:(UIApplication *)application {
nst = [NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(trigger) userInfo:nil repeats:YES];
[window makeKeyAndVisible];
}

The selector must have the following signature:
- (void)timerFireMethod:(NSTimer*)theTimer
so you need
#selector(trigger:)
--edit--
Maybe you are doing this somewhere else, but in the code you included you do not actually start the timer. You have to add it to a NSRunLoop before it can trigger any events at all.
[[NSRunLoop currentRunLoop] addTimer:nst forMode:NSDefaultRunLoopMode];
If I read the examples correctly. I've only used the one the init method that automatically adds it to the current NSRunLoop. You really should look at the developer docs that someone included in the comments to my post.

Two things:
1) as others say, the method should have the following signature..
-(void) trigger:(NSTimer *) theTimer;
and you make the timer thus:
nst = [NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(trigger:) userInfo:nil repeats:YES];
2) merely creating the timer does not run it. As the documentation says:
You must add the new timer to a run
loop, using addTimer:forMode:. Then,
after seconds have elapsed, the timer
fires, invoking invocation. (If the
timer is configured to repeat, there
is no need to subsequently re-add the
timer to the run loop.)
Here's a piece of real functioning code that you can model after. The timer creation is the same as yours, but it also adds it to runloop the right way.
[[NSRunLoop currentRunLoop] addTimer:
[NSTimer timerWithTimeInterval:0.1
target:self
selector:#selector(someSelector:)
userInfo:nil
repeats:NO]
forMode:NSDefaultRunLoopMode];

The selector you're giving the timer, trigger, indicates that it should call a method that takes no parameter. Either change your timer-fired method to
- (void)trigger
{
// look at me, I don't take any parameters
NSLog(#"timer fired");
}
or change your initial timer call to use #selector(trigger:).

Your problem is due to the fact that timerWithTimeInterval:target:selector:userInfo:repeats: creates a timer but does not schedule it on the run loop, you have to do it yourself.
However, you may as well use this method which creates the timer and schedules it on the run loop: scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:

I had a problem when starting timer in - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { not in main thread.
dispatch_async(dispatch_get_main_queue(), ^{
[self startScheduledTimer];
});

Related

Issues with NSInvocation NSTimer and NSMethodSignature

I am trying to get a button to create a NSTimer which in turn will call a function (refreshView) to refresh UI elements, but I am having problems and I am not sure where the problem lies. Is the method signature wrong? Or am I getting the NSRunLoop part wrong? Or is it just horribly off base? Any help is appreciated.
-(IBAction)reload:(id)sender{
NSInvocation *displayInvocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:#selector(refreshView)]];
[displayInvocation setTarget:self];
NSTimer *slideShowTimer = [NSTimer scheduledTimerWithTimeInterval:5.0
invocation:displayInvocation
repeats:YES];
[slideShowTimer fire];
NSRunLoop * a = [NSRunLoop currentRunLoop];
[a addTimer:slideShowTimer forMode:NSRunLoopCommonModes];}
-(void)refreshView{
[slideshow1 displayWithView:MajorImageView topicLabel:TopicLabel];
}
Your code seems very complicated for nothing. Do you want to (1) start a timer to call [refreshView] periodically, or (2) call it later.
For (1), simply setup a timer with,
[NSTimer scheduledTimerWithTimeInterval:(NSTimeInterval)seconds
target:(id)target
selector:(SEL)aSelector
userInfo:(id)userInfo
repeats:(BOOL)repeats]
No need to use a method invocation, a target/action will be enough
For (2), if you want to call it later,
[NSObject performSelector:(SEL)aSelector
withObject:(id)anArgument
afterDelay:(NSTimeInterval)delay]

NSTimer to update label

self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(updateTimerDisplay) userInfo:nil repeats:YES];
[runLoop addTimer:self.timer forMode:NSRunLoopCommonModes];
This code-snippet is copied from my viewDidLoad method, so it is runned from the main-thread. All it do is to call a method to update a label.
I thought I need to have a own thread for doing this, but after getting help on this at SO I figured out that I did not.
However, I do not understand the NSRunLoopCommonModes. Why does it work?
AND the timer updates the label which is a "digital counter" which is on the same screen as a tableview so it CAN'T stop the timer even if the user holds the screen.
Thanks.
A NSRunLoop can run in different input modes. The mode defines which events are handled by the current runloop.
e.g.: If the current runloop is in event tracking mode, it only handles modal event loops. (e.g. dragging a NSScrollBar or a NSSlider on the Mac)
If you add your NSTimer only for NSDefaultRunLoopMode it won't fire if something is causing a modal event loop. (Details in Apple's documentation)
NSRunLoopCommonModes is an "alias" for multiple modes so that you don't have to do:
[[NSRunLoop currentRunLoop] addTimer:mRenderDurationTimer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:mRenderDurationTimer forMode:NSModalPanelRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:mRenderDurationTimer forMode:NSEventTrackingRunLoopMode];
I don't think you have to have this line at all, the first line is enough... I use PSYBlockTimer in my code which derives from the SDK method you use, but instead of a selector calls a block :
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:NO usingBlock:^ (NSTimer *t)
{
// stuff that will get executed in a second
}];
If you add your time to an instance of NSRunLoop under another thread, you need a while loop for this NSRunLoop of the thread. It looks like following:
do {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]];
} while (bDone);
Normally, I add the above code on my thread main function, and when thing is done, the thread go die and the autorelease pool of the thread will be released.

Converting non-main runloop tasks to GCD

I have a task that runs periodically and it was originally designed to run on a separate run loop than the main runloop using NSThread and NSTimer.
What's the best way to adapt this to take advantage of GCD?
Current code:
-(void)initiateSomeTask
{
[NSThread detachNewThreadSelector:#selector(startTimerTask)
toTarget:self withObject:nil];
}
-(void)startTimerTask
{
// We won't get back the main runloop since we're on a new thread
NSRunLoop *myRunLoop = [NSRunLoop currentRunLoop];
NSPort *myPort = [NSMachPort port];
[myRunLoop addPort:myPort forMode:NSDefaultRunLoopMode];
NSTimer *myTimer = [NSTimer timerWithTimeInterval:10 /* seconds */
target:self selector:#selector(doMyTaskMethod)
userInfo:nil repeats:YES];
[myRunLoop addTimer:myTimer forMode:NSRunLoopCommonModes];
[myRunLoop run];
}
Is there anything I can do besides replace detachNewThreadSelector with dispatch_async?
You can replace the use of NSTimer with use of dispatch_source_create with DISPATCH_SOURCE_TYPE_TIMER. You won't need a run loop then.
Back in the original case, though, you don't really need to make a thread or use dispatch to run a timer. Kind of the point of run loops is that you don't need to make a thread to do something simple like a timer.

I should able to delay 2 seconds using NSTimer. How to do it?

I want to produce a delay of 2 seconds using NSTimer how to initialize timer in program?
Multiple options here.
If you just want a delay of 2 seconds you could use the sleep() function
#include<unistd.h>
...
sleep(2);
Or you may be able to use NSTimer like so
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(fireMethod) userInfo:nil repeats:NO];
And in your class you would have a method defined as
-(void)fireMethod
{
//Do stuff
}
Here you go...
[NSTimer scheduledTimerWithTimeInterval:2
target:self
selector:#selector(action)
userInfo:nil
repeats:NO];
Simple answer: [NSThread sleepForTimeInterval:10.0];
Note that you should not really be thinking about delays in an event driven UI/OS. You should thinking about tasks you want to do now, and tasks you want to do later, and code these subtasks and schedule them appropriately. e.g. instead of:
// code that will block the UI when done in the main thread
- (void) methodC {
doA();
delay(2);
doB();
}
you might want to have code that looks more like:
- (void) methodA {
doA();
return; // back to the run loop where other useful stuff might happen
}
- (void) methodB {
doB();
}
and you can then schedule methodB with an NSTimer at the end of methodA, an NSTimer started by whatever called methodA, or, the best option, by the asynchronous completion routine of something started by methodA.

Problems launching an NSTimer from a secondary thread

I'm noticing in my code that when I try to start an NSTimer from a secondary thread, it doesn't work. I tried calling +[NSRunLoop currentRunLoop] just in case the problem was that the thread didn't have a run loop...but no dice. (Note that that was a shot in the dark. The docs said that would create a run loop, but perhaps there's other configuration that I needed to do, and didn't.)
I'm aware of calls like -[NSObject performSelectorOnMainThread:] which could solve my problem (in fact, my solution was to simply move this code into the primary thread, which works fine), but I'm still curious about why this problem occurred. Is it in fact impossible to start an NSTimer from a secondary thread? Is there a workaround?
Thanks very much.
The following code segment works for me.
-(id)init {
myWorkerThread = [[NSThread alloc]initWithTarget:self selector:#selector(workerThread) object:nil];
[myWorkerThread start];
}
#pragma mark WorkerThread Support
-(void)stillWorking {
NSLog(#"Still working...");
}
-(void)workerThread {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
NSTimer *threadTimer = [NSTimer scheduledTimerWithTimeInterval:10
target:self
selector:#selector(stillWorking)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:threadTimer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
[pool drain];
}
I found this page with some source code for starting an NSTimer on a secondary thread. Do you actually start the runloop in your code? It's tough to say without seeing your code what the problem may be:
http://www.iphonedevsdk.com/forum/iphone-sdk-development/22175-nstimer-secondary-thread-will-produce-leaks.html
John Franklin answer is correct..but when you call the method
scheduledTimerWithTimeInterval: target: selector: userInfo: repeats:
it automatically schedules the timer on the current NSRunLoop. Therefore you don't need to add again the timer to the current run loop, you can only call a [[NSRunLoop currentRunLoop] run] method.
Make sure you A) add the timer to the current runloop, and B) run said runloop. When your timer fires, if you'd like to exit the [runloop run] invocation, call [runloop stop].