NSTimer as a timeout mechanism - iphone

I'm pretty sure this is really simple, and I'm just missing something obvious. I have an app that needs to download data from a web service for display in a UITableView, and I want to display a UIAlertView if the operation takes more than X seconds to complete. So this is what I've got (simplified for brevity):
MyViewController.h
#interface MyViewController : UIViewController
<UITableViewDelegate, UITableViewDataSource> {
NSTimer *timer;
}
#property (nonatomic, retain) NSTimer *timer;
MyViewController.m
#implementation MyViewController
#synthesize timer;
- (void)viewDidLoad {
timer = [NSTimer scheduledTimerWithTimeInterval:20
target:self
selector:#selector(initializationTimedOut:)
userInfo:nil
repeats:NO];
[self doSomethingThatTakesALongTime];
[timer invalidate];
}
- (void)doSomethingThatTakesALongTime {
sleep(30); // for testing only
// web service calls etc. go here
}
- (void)initializationTimedOut:(NSTimer *)theTimer {
// show the alert view
}
My problem is that I'm expecting the [self doSomethingThatTakesALongTime] call to block while the timer keeps counting, and I'm thinking that if it finishes before the timer is done counting down, it will return control of the thread to viewDidLoad where [timer invalidate] will proceed to cancel the timer. Obviously my understanding of how timers/threads work is flawed here because the way the code is written, the timer never goes off. However, if I remove the [timer invalidate], it does.

I think there is a problem with scheduling a timer and doing a blocking call on the same thread. Until the blocking call is completed, the run-loop cannot fire the timer.
I suggest you to detach a thread to perform the long operation. Once the long operation is finished, call back on the main thread to invalidate the timer.
Note: it is important to invalidate the timer on the same thread it was scheduled.
- (void)viewDidLoad {
timer = [NSTimer scheduledTimerWithTimeInterval:20
target:self
selector:#selector(initializationTimedOut:)
userInfo:nil
repeats:NO];
[NSThread detachNewThreadSelector:#selector(doSomethingThatTakesALongTime:) toTarget:self withObject:nil];
}
- (void)doSomethingThatTakesALongTime:(id)arg {
sleep(30); // for testing only
// web service calls etc. go here
[self performSelectorOnMainThread:#selector(invalidate) withObject:nil waitUntilDone:NO];
}
- (void)invalidate {
[timer invalidate];
}
- (void)initializationTimedOut:(NSTimer *)theTimer {
// show the alert view
}

Have you tried to use [NSThread sleepforTimeInterval:30]; ?

The sleep() occurs on the main thread and the associated run loop never has the chance to invoke the selector for the timer.
If you would do real work in -doSomething that doesn't block the thread, e.g. non-blocking calls to web-services, it would work as expected. Blocking calls however would have to be done in a different thread so the main run loop does not get blocked.

Related

iPhone SDK: UIButton repeat with repeat delay

I've implemented a repeated task on button (UIButton) hold with the code from another Stack Overflow thread ( UIButton Touch and Hold ).
For various reasons, I'd like a repeat delay. definition of repeat delay
I can't quite wrap my head around how to do this however. I am relatively new to coding for the iPhone.
Inspired in the code you suggested you can go for something like this:
Make an NSTimer that will start up when the button is pressed and fire a method every x seconds.
Header (.h):
// Declare the timer and the needed IBActions in interface.
#interface className {
NSTimer * timer;
}
-(IBAction)theTouchDown(id)sender;
-(IBAction)theTouchUpInside(id)sender;
-(IBAction)theTouchUpOutside(id)sender;
// Give the timer properties.
#property (nonatomic, retain) NSTimer * timer;
Implementation file (.m):
// Synthesize the timer
// ...
Make an IBAction for "Touch Down" to start the timer that will fire the action method every 2 seconds. Then make another IBAction for "Touch Up Inside" and "Touch Up Outside" to invalidate the timer.
For example:
-(IBAction)theTouchDown {
self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0
target:self
selector:#selector(action:)
userInfo:nil
repeats:NO];
}
-(IBAction)theTouchUpInside {
[self.timer invalidate];
self.timer = nil;
}
-(IBAction)theTouchUpOutside {
[self.timer invalidate];
self.timer = nil;
}
Then in that method fired by the NSTimer do whatever you need:
-(void)action:(id)sender {
// ...
}

Facing Difficulty With NSThread While Trying To Animate Label

I'm very new to IOS programming and i have doubt with NSThread.
My problem is, i have a UILabel in my view and i want to hide and make it visible Successively after every 5 second.
For this purpose i've used NSThread as below.
[NSThread detachNewThreadSelector:#selector(animate) toTarget:self withObject:nil];
-(void) animate
{
while(animateLabel){
[NSThread sleepForTimeInterval:5];
if(label.hidden){
NSLog(#"Label is hidden");
[label setHidden:NO];
}else
{
NSLog(#"Label is vissible");
[label setHidden:YES];
}
}
}
Now i'm getting "Label is hidden" and "Label is vissible" Successively in log after every 5 seconds. But my label is not getting hide.
I did with NSTimer and it's working.
But, what is the problem with above code ?. If no problem with this code, Why NSThread couldn't do ?
You need to perform this on a main thread instead.
Try this -
[NSThread performSelectorOnMainThread:#selector(animate) toTarget:self withObject:nil];
since you have while loop
remove sleep and add runloop
[[NSRunLoop currentRunLoop] runUntilDate:(NSDate*)]
UI can be changed only by main thread. instead of creating new thread you can use selector
try this -
[self performSelectorOnMainThread:#selector(animate) withObject:nil afterDelay:5.0 ];
My Bad, didn't read that you purposely intended to do it with NSThread, here's how to do it with NSTimer anyway:
If you create NSTimer from the main thread you don't even need to deal with threading safety-issues.
declare an NSTimer in the Header file so you can reach it in case you want to cancel it or something (I'm also assuming your label is named 'mainLabel' and is declared properly):
NSTimer *labelVisibilityTimer;
#property (nonatomic,retain) NSTimer *labelVisibilityTimer;
in your implementation file, properly synthesize the Timer and initialize it with the method that will trigger the visibility change.
#synthesize labelVisibilityTimer;
- (void)viewDidLoad{
self.labelVisibilityTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:#selector(toggleVisibilityOfLabel) userInfo:nil repeats:YES];
}
-(void)toggleVisibilityOfLabel{
mainLabel.hidden = !mainLabel.hidden;
}
- (void)viewDidUnload{
[super viewDidUnload];
self.labelVisibilityTimer = nil;
}
- (void) dealloc{
[super dealloc];
[labelVisibilityTimer release];
}

How to cancel a previously scheduled event in Objective-C?

I am making an iphone app and have scheduled an event called gameOver to occur after 15 seconds(when the game is over).
[self performSelector:#selector(gameOver) withObject:nil afterDelay:15.0];
How can I unschedule this event so that if someone wanted to press a reset button and start the game over, this scheduled event would be deleted and another one would be created.
I would use an NSTimer
with the retain-property NSTimer *myTimer
-(void) startTimer
{
self.myTimer = [NSTimer scheduledTimerWithTimeInterval:myDelay target:self selector:#selector(myTimerExpiredMethod:) userInfo:nil repeats:NO];
}
-(void) myTimerExpiredMethod:(id)aSender
{
NSLog(#"timer expired");
self.myTimer = nil;
}
-(void) cancelTimer
{
[self.myTimer invalidate];
self.myTimer = nil;
}
Try
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget
aTarget would be the object the selector should be performed on. self in your example.
Edit:
This method is defined on NSObject so:
[NSObject cancelPreviousPerformRequestsWithTarget:myObject]
would be the syntax.
Edit 2:
Use
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(id)anArgument
to only unschedule calls to this specific selector.

NSTimer fails to invoke methord

Following is my sample code.
#interface TrackTimer : NSObject {
NSTimer *timer;
}
#property (nonatomic, retain) NSTimer *timer;
- (void) startTimer;
- (void) stopTimer;
- (void) timerFired;
#end
TrackTimer.m
#synthesize timer;
- (void) startTimer
{
NSLog(#"Timer started ...");
if(timer)
{
timer = nil;
}
timer = [NSTimer scheduledTimerWithTimeInterval:30.0 target:self selector:#selector(timerFired) userInfo:nil repeats:NO];
}
- (void) stopTimer
{
NSLog(#"Timer stoped ...");
[tTimer invalidate];
}
- (void) timerFired
{
NSLog(#"Timer Fired ... :)");
}
I have to use the same timer object from 3 different view controllers, my problem is startTimer method do not invoke timerFired method in 2nd UIViewController. Its works perfectly on 1st and 3rd View Controller.
appln Flow : 1stView -> 2ndView -> 3rdView
You are doing everything right... almost.
Your timer does not fire, because of the "if" statement.
if (timer) {
timer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:#selector(boom) userInfo:nil repeats:NO];
}
Here, the "if" statement returns NO, because the timer is not yet initialized..
The fact that you make it a property and synthesize it does not mean that (timer != nil)
If you remove the "if" statement it should work...
From the Apple docs on NSTimer:
The message to send to target when the timer fires. The selector must have the following signature:
- (void)timerFireMethod:(NSTimer*)theTimer
So, it looks like the signature of your timerFired method needs to be expanded to include one parameter '(NSTimer*)theTimer' and your selector needs to be #selector(timerFired:)
Don't really know how you do that, but NStimer has a class method called
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats. So you can do it like this:
timer=[NSTimer scheduledTimerWithTimeInterval:2 target:self
selector:#selector(timerFired)
userInfo:nil
repeats:YES];
This will invoke the timerFired method for you.
P.S.Here's the link to a simple app that does just what you want.
http://www.mediafire.com/?8uz115drqzb2nan

iphone multiple NSTimer troubles

I'm still new to programming so excuse me if this is silly. I'm programming a simple game and require multiple timers to send different messages at different intervals, so when creating the game, the following is called:
[self gameTimerValidate];
[self scoreTimerValidate];
- (void) gameTimerValidate
{
gameTimer = [NSTimer scheduledTimerWithTimeInterval:[myGame gIntervalSpeed] target:self selector:#selector(gameTimerInterval:) userInfo:nil repeats:YES];
}
- (void) scoreTimerValidate
{
scoreTimer = [NSTimer scheduledTimerWithTimeInterval:0.02 target:self selector:#selector(scoreTimerInterval:) userInfo:nil repeats:YES];
}
I have the scoreTimer and gameTimer declared in my header file ("NSTimer *gameTimer;"). I invalidate the timers when pausing the game or completing the level, and call the above methods again when resuming the game or entering the next level, respectively.
I spent hours today trying to figure out why pausing the game would crash the application. After doing some debugging I noticed the retain count of gametimer was 0, and for scoretimer it was 2. Of course, I can't invalidate a timer with a retain count of 0, but I'm not sure how that came about.
Is there a specific way I must initialize two different NStimers? I been searching for hours on this to no avail...
NSTimer is a tricky class. It doesn't behave like you expect it to.
Firstly, the timer instances are not finally retained by the objects that initialize them but by IIRC, the NSRunLoop. This means that if you have an object that creates a timer, the timer will continue to be active even if you destroy the object that created it and all other references in your custom code. The timer will keep going along firing off messages and you have no clue where they're coming from.
Secondly, you can't stop/pause and resume a timer. When you invalidate it, it's dead.
I suggest creating a light class that will manage the timers for you so you don't have to keep track of it in the rest of your code. e.g.
#interface SDL_SimpleTimerController : NSObject {
NSTimer *currentTimer;
NSTimeInterval theInterval;
id theTargetObj;
SEL theSelector;
BOOL timerIsRunning;
}
#property (nonatomic,retain) NSTimer *currentTimer;
#property NSTimeInterval theInterval;
#property (nonatomic,retain) id theTargetObj;
#property SEL theSelector;
#property BOOL timerIsRunning;
-(SDL_SimpleTimerController *) initWithInterval:(NSTimeInterval)anInterval forTarget:(id)aTargetObj andSelector:(SEL)aSelector;
-(void) startTimer;
-(void) stopTimer;
#end
#implementation SDL_SimpleTimerController
#synthesize currentTimer;
#synthesize theInterval;
#synthesize theTargetObj;
#synthesize theSelector;
#synthesize timerIsRunning;
-(SDL_SimpleTimerController *) initWithInterval:(NSTimeInterval) anInterval forTarget:(id) aTargetObj andSelector:(SEL) aSelector
{
self=[super init];
theInterval=anInterval;
theTargetObj=aTargetObj;
theSelector=aSelector;
timerIsRunning=NO;
return self;
}// end initWithInterval:
-(void) startTimer{
if (currentTimer) {
currentTimer=Nil;
}
currentTimer=[NSTimer scheduledTimerWithTimeInterval:theInterval target:theTargetObj selector:theSelector userInfo:Nil repeats:YES];
timerIsRunning=YES;
}//end startTimer
-(void) stopTimer{
if (currentTimer) {
[currentTimer invalidate];
currentTimer=Nil;
}
timerIsRunning=NO;
}// end stopTimer
- (void)dealloc {
if (currentTimer) {
[currentTimer release];
currentTimer=Nil;
}
[theTargetObj release];
theTargetObj=Nil;
[super dealloc];
}
The timers are not reusable. After you invalidate them they are removed from the run loop and their retain count is decremented, resulting in their deallocation the next time through the loop. You'll either have to create new ones or stop invalidating them.
I think you should try to find where you might be doing a [scoreTimer retain], and where you might be invalidating (or releasing) gameTimer more than once (you only need to do the latter, if where you checked the retainCount, was after you had invalidated once). You can't increase the retainCount by calling either of
[self gameTimerValidate];
[self scoreTimerValidate];
more than once. You would leak memory, and have two timers firing at the same interval, but you wouldn't have one of those timers have a higher retainCount because of that.
If those two instance variables were retained properties, and you were setting them using self.gameTimer = ..., then I can see them getting retained an extra time. But the code I see doesn't explain your problem.
Search all instances of those two timers and see what else might be messing with things.
One suggestion, you might want to check for nil, like this:
- (void) gameTimerValidate
{
if (gameTimer == nil)
gameTimer = [NSTimer scheduledTimerWithTimeInterval:[myGame gIntervalSpeed] target:self selector:#selector(gameTimerInterval:) userInfo:nil repeats:YES];
}
- (void) scoreTimerValidate
{
if (scoreTimer == nil)
scoreTimer = [NSTimer scheduledTimerWithTimeInterval:0.02 target:self selector:#selector(scoreTimerInterval:) userInfo:nil repeats:YES];
}
- (void) invalidateMyTimers {
[gameTimer invalidate], gameTimer = nil;
[scoreTimer invalidate], scoreTimer = nil;
}
Thanks for the replies, after giving it some thought I'm going to go with an approach similar to what TechZen said, and and just keep the timers running with a BOOL variable, and using that variable for checking events like pause and such (ie changing the boolean vs stopping and starting the timers).
(also my first time using this website, still learning the format of where the answers go) thanks again!
In reply to TechZen's light class, I think you should not release the target object above as you did not create it (therefore don't own it)
(void)dealloc {
if (currentTimer) {
[currentTimer release];
currentTimer=Nil;
}
**[theTargetObj release];**
theTargetObj=Nil;
[super dealloc];
}