To which source file does NSRunLoop/NSTimer belong? - iphone

I've been trying to figure out how to setup an NSTimer to allow me to print the current time in a UILabel within a View, and have it update every second (no finer resolution required - just a simple clock).
At first, I wasn't using a NSRunLoop, but if I try and include one, the execution just "spins" inside the loop, blocking further execution. I have posted my code below.
-(id) printCurrentTime {
now = [NSDate date];
dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setTimeStyle:NSDateFormatterMediumStyle];
NSString *nowstr = [dateFormat stringFromDate:now];
[dateFormat release];
NSLog(#"Current time is: %#",nowstr);
return nowstr;
}
And in the ViewController source file, I execute as per:
TimeStuff *T = [[TimeStuff alloc] init];
NSString *thetime = [T printCurrentTime];
[timelabel setText:thetime];
[T release];
[self.view addSubview:timelabel];
NSTimer *timmeh = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(printCurrentTime) userInfo:nil repeats:YES];
[[[NSRunLoop currentRunLoop] addTimer:timmeh forMode:NSDefaultRunLoopMode] run];
The "TimeStuff" class is effectively an empty class, save for the printCurrentTime function.
Questions:
1) Should I be including the RunLoop in the AppDelegate class? I am having trouble visualising how this all should hang together, as in - what are the steps to achieving a Loop based on Timer to update a text label with the up-to-second time. Pretty stumped.
2) In the event that I should use a NSThread, should that also be in it's own class / the Delegate class.
3) Is the ViewController class totally out of bounds for looping/timers, and simply the "eye candy" class, with callbacks to functions in the Delegate class?
Thank you for your time and patience.

You don't need to deal with run loops at all.
This line :
NSTimer *timmeh = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(printCurrentTime) userInfo:nil repeats:YES];
will create a timer and attach it to the current thread's run loop for you. You don't need the [NSRunLoop addTimer:forMode:] call at all - you can delete that line.
PS You certainly don't need to go as far as NSThreads!
EDIT Regarding your comment :
You will need to make an instance of your TimeStuff class for the timer to use if that's where your printCurrentTime method is. i.e.
#interface MyViewController : UIViewcontroller {
TimeStuff *timeStuff
}
and in your viewDidLoad method:
- (void)viewDidLoad {
[super viewDidLoad];
...
// Create our timestuff if we don't have one already
if (nil == timeStuff)
timeStuff = [[TimeStuff alloc] init];
// Start the timer
[NSTimer scheduledTimerWithTimeInterval:1.0 target:timeStuff selector:#selector(printCurrentTime) userInfo:nil repeats:YES];
and not forgetting dealloc
- (void)dealloc {
[timeStuff release];
...
[super dealloc];
}
Passing in the timeStuff as the target for the timer tells it where to look for the printCurrentTime method!
Hope that helps,
PS All the line #class TimeStuff does is tell the compiler that there is a class called TimeStuff. It has no idea that you want to use it for your timer's selector!

Related

NSTimer not stopping?

I'm trying to to stop an NSTimer with the following code:
- (void)viewDidLoad
{
[super viewDidLoad];
timer3 = [NSTimer timerWithTimeInterval:5.0 target:self selector:#selector(start) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer3 forMode:NSDefaultRunLoopMode];
}
-(void)invalidate
{
[timer3 invalidate];
timer3 = nil;
}
and I call -(void)invalidate from another class like this:
-(void)timer
{
ClassOfMyTimer *class = [[ClassOfMyTimer alloc] init];
[class invalidate];
}
but the timer doesn't stop. Does anyone know what I'm doing wrong?
You need to call your invalidate method on the same instance of your class that created the timer. In your timer method you create a new instance of your class which could have its own timer and invalidate that.
I'm kind of confused by what you're trying to do here, but I'd guess that you're not maintaining a reference to timer3.
Have you created a property in the .h file for the timer:
#property (strong) NSTimer *timer3;
And then added a synthesize statement in the .m file:
#synthesize timer3;
Then, in viewDidLoad:, you can maintain a reference to the timer you're creating via:
self.timer3 = [[[NSTimer timerWithTimeInterval:5.0 target:self selector:#selector(start) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:self.timer3 forMode:NSDefaultRunLoopMode];
And, to invalidate the timer later:
[self.timer3 invalidate]
self.timer3 = nil
On preview, Sven also has a valid solution to an issue that might be impacting you..

NSTimer scheduledTimerWithTimeInterval: repeats:NO selector method calls multiple times

I am using NSThread along with NSTimer.
My code is like this
-(void) checkForRecentAlarm
{
if ([self.alarmThread isFinished])
{
[self.alarmThread cancel];
}
self.alarmThread = [[NSThread alloc] initWithTarget:self selector:#selector(startTimerForRecentAlarm) object:nil];
[self.alarmThread start];
//[NSThread detachNewThreadSelector:#selector(startTimerForRecentAlarm) toTarget:self withObject:nil];
}
-(void)startTimerForRecentAlarm
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
self.recentAlarmTime = [NSDate date];
self.dbObject = [[RADataBaseModelManager alloc] init];
self.recentAlarmTime = [self.dbObject getMostRecentAlarmTimeFromDB];
if (self.recentAlarmTime) {
NSTimeInterval timeIntervalToAlarm = [self.recentAlarmTime timeIntervalSinceNow];
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
//Fire timer every second to updated countdown and date/time
self.RATimer = [NSTimer scheduledTimerWithTimeInterval:timeIntervalToAlarm target:self selector:#selector(timerFireMethod:) userInfo:nil repeats:NO];
[runLoop run];
}
[pool release];
}
- (void)timerFireMethod:(NSTimer*)theTimer
{
[self.RATimer invalidate];
[theTimer invalidate];
self.RATimer = NULL;
theTimer = NULL;
[self playAlarm];
UIAlertView *alarmAlert = [[UIAlertView alloc] initWithTitle:#"Alarm" message:#"" delegate:self cancelButtonTitle:#"Close" otherButtonTitles:#"Snooze", nil];
[alarmAlert show];
[alarmAlert release];
alarmAlert = nil;
}
Now the problem is, my alertbox comes twice for one call in the
startTimerForRecentAlarm
method. So that the alert comes consequently twice and my view get stuck.
What will be problem here?
I am trying to implement an alarm with multiple alarm option using a single NSTimer.
Please help.
When I debug this, I can find many simultaneous threads are running on same code(UIAlertView).
I can't see any obvious reason why that would be called twice, but it does seem like an overly complex way of doing what you need to do.
Have you thought about using local notifications?
If you don't want to do that, you could refactor your code so it works like this:
1. Add a new event
2. If there's no timer or the time to the event is shorter than the time on the timer, then set the timer for this event.
3. When a timer fires, check for the next event and set a timer for that event (if there is one).
This does seem really complex. My general observation is that if you get two timer firings it's because you have two timers for some reason.
If you have multiple threads doing UIAlertView, you have another problem, because you can only (reliably) do UI from the main thread.

switching from view 1 to view 2 without pressing any buttons

How to switch from view1 to view 2 without having any button pressed. In my views i have is uiimageview and uitextview
With the NSTimer i m trying to do this
in the viewdidload method by using the following code:
In the firstviewcontroller.h file
#interface FirstViewController : UIViewController
{
NSTimer *SwitchingTimer;
}
In the firstviewcontroller.m file
- (void)viewDidLoad
{
[super viewDidLoad];
SwitchingTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(SwitchView) userInfo:nil repeats:YES];
}
-(void)SwitchViews:(id)sender
In the secondviewcontroller.m file
-(void) SwitchView
{
SecondViewController *SecondView = [[SecondViewController alloc]
initWithNibName:#"SecondViewController" bundle:nil];
SecondView.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:SecondView animated:YES];
[SwitchingTimer invalidate];
self.SwitchingTimer = nil;
}
but nothing is happening. Can someone please tell me what i m missing in my code. Will appreciate help.
Thanks in advance.
There are a few issues in your code that are worth mentioning though I am not sure if those will provide you a solution.
Why do you want to repeat the timer every 2 seconds. I think you just want to switch to next view only once and if so then dont repeat the timer. So no need to invalidate the timer.
Your code for the SwitchView method is leaking memory. Please make sure that the SecondView is released after presenting the modal view(in case you are not using ARC).
Please follow the standard naming conventions. For eg: methods and variables should start with lowercase.
Regarding your issue please make sure that the nib name is correct and you are getting a valid object for the second view controller. You can check by using NSLog. Also ensure that the method Switchview is called. Try putting a break point and verify that it is called.
Another Option
If you just want to switch the view only once you can go for another option which does not make use of the NSTimer. For this, you can use performSelector:withObject:afterDelay:. This is just another option for the scenario I mentioned above.
You need to add it to the run loop:
- (void)viewDidLoad
{
[super viewDidLoad];
SwitchingTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(SwitchView) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer: SwitchingTimer forMode:NSDefaultRunLoopMode];
}
Also, you could rename -(void)SwitchView to -(void)switchViewTimerDidFire:(NSTimer *)timer. From the Documentation:
[...] The selector must have the following signature:
- (void)timerFireMethod:(NSTimer*)theTimer
The timer passes itself as the argument to this method.

Set Timer from another class to fire in another view

EDIT: Added the NSRunLoop from my code, as mentioned by Deepak below. This was originally in my code and forgot to add as it was commented out.
I have 2 classes: MainViewController, and ConfigViewController. The user switches to the ConfigView and uses a UIDatePicker / UIButton combo to set a Date/Time. Upon grabbing the correct time from the UIDatePicker object, I setup a NSTimer to fire as per the following code:
ConfigViewController.m
-(IBAction)setAlarmDate:(id)sender {
//Instantiate to get access to doAlarm:
MainViewController *mvc = [[MainViewController alloc] init];
dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateStyle:NSDateFormatterMediumStyle];
[dateFormat setTimeStyle:NSDateFormatterShortStyle];
NSString *target = [NSString stringWithFormat:#"%#",[dateFormat stringFromDate:datePicker.date]];
alarmDate = [datePicker date];
mvc.fireTimer = [[NSTimer alloc]
initWithFireDate:alarmDate interval:1 target:mvc
selector:#selector(doAlarm:) userInfo:nil repeats:YES];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:mvc.timer forMode:NSDefaultRunLoopMode];
[self dismissModalViewControllerAnimated:NO];
}
The doAlarm: method is as follows:
MainViewController.m
- (void)doAlarm:(NSTimer *)timer {
NSLog(#"Called doAlarm:");
UIImage *ac = [UIImage imageNamed:#"alarmclock.png"];
[self.alarmview setImage:ac];
[self.alarmview setHidden:NO];
[self.view addSubview:alarmview];
[self.view bringSubviewToFront:alarmview];
}
However, when I set the alarm date, the timer fails to fire. I think the following problems are afoot:
1) I am instantiating a new instance of the MainViewController class, setting the Timer going and then passing control back to the "original" instance of MainViewController when I dismiss the MVC. At this point, probably the "new" instance of MainViewController is nothing but a dangling pointer, and is never referenced again anyway, hence no segfault.
2) doAlarm: references self.view, which is supposed to be the MainViewController.view, but as it's instantiated in the scope of ConfigViewController, the alarm image would never be seen anyway...
I imagine my theories are a bit unfounded, but with my current level of knowledge, they make sense to me.
Any light you can shed on the above would be smashing.
Many thanks!
swisscheese.
If you go to the documentation for initWithFireDate:interval:target:selector:userinfo:repeats:, you will notice that while the timer is initialized it is not scheduled yet. You will need to add it a NSRunLoop to make it work. Do this after you have initialized an alarm as above.
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
You can also consider using the scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: which does that for you.
There are of course few other things here.
New MainViewController object
You guessed it right. You are creating an unrelated object which will just leak. To solve this you can maintain a weak reference to the MainViewController object. You can do this as follows -
#class MainViewController; // Forward declaration
#interface ConfigViewController {
...
MainViewController *mainViewController;
}
...
#property (nonatomic, assign) MainViewController *mainViewController;
...
#end
in ConfigViewController.m:
#import "MainViewController.h"
#implementation ConfigViewController
#synthesize mainViewController;
...
- (IBAction)setAlarmDate:(id)sender {
dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateStyle:NSDateFormatterMediumStyle];
[dateFormat setTimeStyle:NSDateFormatterShortStyle];
alarmDate = [datePicker date];
mainViewController.fireTimer = [[NSTimer alloc]
initWithFireDate:alarmDate interval:1 target:mainViewController
selector:#selector(doAlarm:) userInfo:nil repeats:YES];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:mainViewController.timer forMode:NSDefaultRunLoopMode];
mainViewController = nil;
[self dismissModalViewControllerAnimated:NO];
}
...
#end
Timer
I am not sure if you need a repeating timer here. There is an alternative - performSelector:withObject:afterDelay:. This will invoke a method on an object this is called after the desired delay. Does what it calls itself. It will be something like this -
[mvcReference performSelector:#selector(doAlarm)
withObject:nil
afterDelay:[datePicker.date timeIntervalSinceNow]];
doAlarm:
I did not get what you wanted to know here but you are right. self is a reference to the MainViewController object. Unless you retained the view controller that you displayed modally, it would have been dismissed and deallocated by then.

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