I wanted to be able to Pause/ Resume my NSTimer and found this below answer:
NSDate *pauseStart, *previousFireDate;
-(void) pauseTimer:(NSTimer *)timer {
pauseStart = [[NSDate dateWithTimeIntervalSinceNow:0] retain];
previousFireDate = [[timer fireDate] retain];
[timer setFireDate:[NSDate distantFuture]];
}
-(void) resumeTimer:(NSTimer *)timer {
float pauseTime = -1*[pauseStart timeIntervalSinceNow];
[timer setFireDate:[previousFireDate initWithTimeInterval:pauseTime sinceDate:previousFireDate]];
[pauseStart release];
[previousFireDate release];
}
which works fine. However when testing my App for Leaks, it tells me that I get a leak in here:
[timer setFireDate:[previousFireDate initWithTimeInterval:pauseTime sinceDate:previousFireDate]];
Can anyone help me? You can see from the code i used that pauseStart and previousFireDate are retained in the pauseTime method and released in the resumeTimer method.
Many thanks
Sam
You should never call init (or any of the other methods in the same family) on an object twice. (Here's why). Change that line to this:
[timer setFireDate:[NSDate dateWithTimeInterval:pauseTime sinceDate:previousFireDate]];
and you'll be fine.
Related
I have the following method to update a label which show a simple time up
-(void)updateTimeLabel:(NSTimer *)timer{
NSInteger secondsSinceStart = (NSInteger)[[NSDate date] timeIntervalSinceDate:_startTime];
NSInteger seconds = secondsSinceStart % 60;
NSInteger minutes = (secondsSinceStart / 60) % 60;
NSInteger hours = secondsSinceStart / (60 * 60);
NSString *result = nil;
if (hours > 0) {
result = [NSString stringWithFormat:#"%02d:%02d:%02d", hours, minutes, seconds];
}
else {
result = [NSString stringWithFormat:#"%02d:%02d", minutes, seconds];
}
_totalTime = result;
_totalTimeLabel.text = result;
}
I then call this as the action to a button:
-(IBAction) startTimer{
_startTime = [NSDate date];
_walkRouteTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(updateTimeLabel:) userInfo:nil repeats:YES];
[_walkRouteTimer fire];
}
But when I run the app I get a bad access error and the app crashes, can anyone help me with this?
Thanks in advance
Are you using ARC? If not, _startTime = [NSDate date]; this line will cause your problem. [NSDate date] returned an autorelease object and _startTime will not hold it if you are not using ARC(or using ARC but declared _startTime as weak).
If so, try to add a retain to it
-(IBAction) startTimer{
//_startTime = [NSDate date]
_startTime = [[NSDate date] retain];
_walkRouteTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(updateTimeLabel:) userInfo:nil repeats:YES];
[_walkRouteTimer fire];
}
And when you finished your timer, after calling of [_walkRouteTimer invalidate], call [_startTime release].
Or even simpler, if you use property for startTime and declared it as retain. Just use dot notation:
-(IBAction) startTimer{
//_startTime = [NSDate date]
self.startTime = [NSDate date];
_walkRouteTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(updateTimeLabel:) userInfo:nil repeats:YES];
[_walkRouteTimer fire];
}
...
//After [_walkRouteTimer invalidate]
self.startTime = nil;
Try adding an exception breakpoint to see which line is crashing:
1) Click on the breakpoint tab (second from the right)... kinda looks like a right pointing arrow or "next" button
2) Click on the "+" in the bottom left corner of the tab menu
3) Select "Add Exception Breakpoint"
4) (Optional) Select "Exception" drop down and change to "Objective-C"
5) Select "Done"
6) Run your code again and try to generate the crash... when you do, it will hopefully be caught by this breakpoint, and you'll see which line is crashing and be able to fix it
Good luck
Tested app in Instrumens for memory leak getting multiple leaks for using multiple times pauseTimer and resumeTimer.
Defined in m file
NSDate *pauseStart, *previousFireDate;
-(void)pauseTimer{
pauseStart = [[NSDate dateWithTimeIntervalSinceNow:0] retain];
previousFireDate = [[timer fireDate] retain];
[timer setFireDate:[NSDate distantFuture]];
}
-(void)resumeTimer{
float pauseTime = -1*[pauseStart timeIntervalSinceNow];
//[timer setFireDate:[previousFireDate initWithTimeInterval:pauseTime sinceDate:previousFireDate]];
[timer setFireDate:[NSDate dateWithTimeInterval:pauseTime sinceDate:previousFireDate]];
}
how to fix this memory leak for pauseTimer and resumeTimer. User can use PauseTimer and resumeTimer multiple times.
Appreciate help.
Thanks.
You are retaining the values for pauseStart and previousFireDate each time the pauseTimer method is fired. You need to release the previous values before retaining the new values in order to avoid a memory leak.
Easiest solution is to create two retained properties for those values (possibly in a class extension), so when you set new values, the release is handled for you. (Just don't forget to release the values in your dealloc method to also avoid leaks.)
You can use NSTimer's isValid property and invalidate method.
-(void)pauseTimer
{
if ( !timer.isValid )
return;
// remember your fire date
[timer invalidate];
}
-(void)resumeTimer
{
if ( timer.isValid )
return;
// schedule your timer to new date
// release old date
}
I've got a timer
myTimer = [NSTimer scheduledTimerWithTimeInterval:x target:self selector:#selector(timerAction:) userInfo:[NSDictionary dictionaryWithObjectsAndKeys:y, #"y", z, #"z", nil] repeats:YES];
and method:
-(void)timerAction:(NSTimer *)theTimer {
SomeObject *y = [[theTimer userInfo] objectForKey:#"y"];
SomeObject *z = [[theTimer userInfo] objectForKey:#"z"];
{etc...}
NSLog(#"Done");}
Now my question is how to call the method directly? I need to turn it on immediately not only after x time passed, so i wanted to call it. However this method returns crash([__NSCFDictionary userInfo]: unrecognized selector sent to instance):
[self performSelector:#selector(timerAction:) withObject:[NSDictionary dictionaryWithObjectsAndKeys:y, #"y", z, #"z", nil]];
As Vignesh suggested, you could just call the NSTimer with a time interval of 0. However, this could cause problems in the future.
You would be better to have a function that does the processing,
-(void)doProcessing:(NSDictionary *)theData {...
And then have a timer method
-(void)timerAction:(NSTimer *)timer {
[self doProcessing:[timer userInfo]];
}
So you can then call it through the timer as you currently have, or you can call it directly with [self doProcessing:data]
If you do it this way, then you will be much better placed if in the future you decide that after doing the processing you want to re-schedule the timer according to certain conditions but only if it was called through the timer as you can then do that in your timerAction method. You should try to get the functionality into as fine grained methods as possible, so don't mix the timer related bits with the processing bits.
Try this (before trying to call the selector) :
NSTimer* t = [[NSTimer alloc] init];
[[t userInfo] setObject:y forKey:#"y"];
[[t userInfo] setObject:x forKey:#"x"];
Then call it, either by :
[self performSelector:#selector(timerAction:) withObject:t];
or :
[self timerAction:t];
Why not do it like Vive wanted to originally, but do some introspection in the timerAction method to see what class the argument is:
-(void)timerAction:(id) param {
NSDictionary *dict = [param isKindOfClass:[NSDictionary class]]? param : [(NSTimer *)param userInfo];
id y = [dict objectForKey:#"y"];
id z = [dict objectForKey:#"z"];
NSLog(#"%# %#",y,z);
}
you can do this,
[NSTimer scheduledTimerWithTimeInterval:0 target:self selector:#selector(timerAction:) userInfo:[NSDictionary dictionaryWithObjectsAndKeys:y, #"y", z, #"z", nil] repeats:NO];
myTimer = [NSTimer scheduledTimerWithTimeInterval:x target:self selector:#selector(timerAction:) userInfo:[NSDictionary dictionaryWithObjectsAndKeys:y, #"y", z, #"z", nil] repeats:YES];
The problem with this code is that when the while loop is executing, the memory usage is increasing continuously. I want to know why does this code keeps on increasing memory when its in while loop.
Which object here is eating the memory. What can i do so that the memory does not increase in the loop.
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSNumber *totalTime = [[self nowPlayingItem] valueForProperty:MPMediaItemPropertyPlaybackDuration];
while (self.currentPlaybackTime < [totalTime floatValue])
{
NSNumber *currentTime = [[NSNumber alloc] initWithFloat:self.currentPlaybackTime];
if([[NSThread currentThread] isCancelled])
{
[NSThread exit];
[newThread release];
}
else if([totalTime intValue] - [currentTime intValue] == 1.0)
{
if(currentTime)
[currentTime release];
break;
}
[currentTime release];
}
[pool release]
That is some inefficient code you have there.... Heres a replacement that doesn't go mental allocating memory for no reason, further more you might want to put a sleep in there so it doesn't eat CPU
// not needed any more
//NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
int totalTime = [[[self nowPlayingItem] valueForProperty:MPMediaItemPropertyPlaybackDuration] intValue];
while (self.currentPlaybackTime < totalTime)
{
//not needed any more
//NSNumber *currentTime = [[NSNumber alloc] initWithFloat:self.currentPlaybackTime];
if([[NSThread currentThread] isCancelled])
{
[NSThread exit];
[newThread release];
}
else if((totalTime - self.currentPlaybackTime) <= 1.0) // you may never hit 1
{
break;
}
}
//[pool release]
I have solved this problem. Replaced the while loop with Timer and made few changes.
Created a timer which fires every second
timer = [NSTimer timerWithTimeInterval:1
target:self
selector:#selector(performAction)
userInfo:nil
repeats:YES];
then in performAction, check for the current playback time and invalidate the timer it the difference in time is <= 1 sec
int totalTime = [[[self nowPlayingItem] valueForProperty:MPMediaItemPropertyPlaybackDuration] intValue];
if((totalTime - self.currentPlaybackTime) <= 1.0)
{
if(timer)
[timer invalidate];
/* Performed my desired action here.... */
}
hello
I am developing small gameApp.
I need to pause the timer,when user goes to another view [say settings view].
when user comes back to that view , I need to resume the timer.
can anybody solve this issue ...
Thanks in Advance...
NSTimer does not give you the ability to pause it. However, with a few simple variables, you can create the effect yourself:
NSTimer *timer;
double timerInterval = 10.0;
double timerElapsed = 0.0;
NSDate *timerStarted;
-(void) startTimer {
timer = [NSTimer scheduledTimerWithTimeInterval:(timerInterval - timerElapsed) target:self selector:#selector(fired) userInfo:nil repeats:NO];
timerStarted = [NSDate date];
}
-(void) fired {
[timer invalidate];
timer = nil;
timerElapsed = 0.0;
[self startTimer];
// react to timer event here
}
-(void) pauseTimer {
[timer invalidate];
timer = nil;
timerElapsed = [[NSDate date] timeIntervalSinceDate:timerStarted];
}
This has been working out quite well for me.
You can't pause a timer. However, when the user goes to the settings view, you can save the fireDate of the timer and also the current date. After this you invalidate the timer and let the user do his/her stuff.
Once he/she switches back to the game, you create a new timer object and set the fire date to the old fire date plus the time the user was in the menu (oldTime + currentTime).
you can use this code to implement pause and resume functionality in NSTimer
//=========new timer update method=============
-(void) updateTimer {
NSDate *currentDate = [NSDate date];
NSTimeInterval timeInterval = [currentDate timeIntervalSinceDate:startDate];
NSDate *timerDate = [NSDate dateWithTimeIntervalSince1970:timeInterval];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"mm:ss.S"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
NSString *timeString = [dateFormatter stringFromDate:timerDate];
lblMessage.text = timeString;
pauseTimeInterval = timeInterval;
}
-(IBAction) startBtnPressed:(id)sender
{
//=============================new update with start pause==================
if(running == NO) {
running = YES;
startDate = [NSDate date] ;
startDate = [[startDate dateByAddingTimeInterval:((-1)*(pauseTimeInterval))] retain];
stopWatchTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/10.0 target:self selector:#selector(updateTimer) userInfo:nil repeats:YES];
}
else {
running = NO;
[stopWatchTimer invalidate];
stopWatchTimer = nil;
[self updateTimer];
}
}
declare in .h file NSDate startDate;
NSTimeInterval pauseTimeinterval;
and in viewdidload pausetimeInterval=0.0; good luck
You can't pause an NSTimer. You can, however invalidate it and create a new one when needed.
on Start -
startNewCapture = [NSDate date];
on Pause -
captureSessionPauseDate = [NSDate date];
captureSessionTimeInterval = [captureSessionPauseDate timeIntervalSinceDate:startNewCapture];
on Resume -
NSDate *dateNow = [NSDate date];
startNewCapture = [NSDate dateWithTimeInterval:-captureSessionTimeInterval sinceDate:dateNow];
- (IBAction)pauseResumeTimer:(id)sender {
if (timerRunning == NO) {
timerRunning = YES;
[pauseTimerBtn setTitle:#"Resume" forState:UIControlStateNormal];
NSString *stringVal = [NSString stringWithFormat:#"%#",timeTxt.text];
stringVal = [stringVal stringByReplacingOccurrencesOfString:#":" withString:#"."];
float tempFloatVal = [stringVal floatValue];
int minuteValue = floorf(tempFloatVal);
float tempSecVal = [stringVal floatValue] - floorf(tempFloatVal);
int secondVal = tempSecVal*100;
minuteValue = minuteValue*60;
oldTimeValue = minuteValue + secondVal;
[timer invalidate];
timer = nil;
}
else
{
timerRunning = NO;
[pauseTimerBtn setTitle:#"Pause" forState:UIControlStateNormal];
startDate = [NSDate date];
timer = [NSTimer scheduledTimerWithTimeInterval:0.25 target:self selector:#selector(timer:) userInfo:nil repeats:YES];
}
}
- (void)runTimer:(NSTimer *)timer {
NSInteger secondsAtStart = (NSInteger)[[NSDate date] timeIntervalSinceDate:startDate];
secondsAtStart = secondsAtStart + oldTimeValue;
NSInteger seconds = secondsAtStart % 60;
NSInteger minutes = (secondsAtStart / 60) % 60;
NSInteger hours = secondsAtStart / (60 * 60);
NSString *result = nil;
result = [NSString stringWithFormat:#"%02ld:%02ld",(long)minutes,(long)seconds];
timeTxt.text = result;
}
Did you end up figuring it out? I saw that you said that you cannot invalidate your timer when you go into another view. Can you explain what you mean by that? NSTimers cannot be paused, and methods to simulate pausing them usually involve invalidating them. You can then simulate "unpausing" by creating a new timer that will then start up again. This will simulate a timer being paused and unpaused.
For people who would like a potentially more convenient method, I wrote a controller class that can conveniently handle pausing and unpausing timers. You can find it here: https://github.com/LianaChu/LCPausableTimer
You can use the controller class that I wrote to create new timers, and then you can pause and unpause the timers by call the methods "pauseTimer" and "unpauseTimer" on the controller class.
I would greatly appreciate any comments or feedback like how you used it, what changes you would like to see, or any features you would like me to add. Please don't hesitate to reply with your comments here, or post on the issues tab on the Github page.