I constantly get malloc error : double freed....
The echoTime function is being called by a button. When I press the button again before the timer ends it gives me the malloc error. When I press the button after the timer finished to start it again, the simulator hangs.
Does anyone know what is wrong with the following piece of code:
-(IBAction)echoTime: (id) sender {
if (gameTimer != nil) {
[gameTimer invalidate];
[gameTimer release];
}
NSInteger secs = 1 * 60;
if (secs != 0) {
NSNumber *elapsedSeconds = [[NSNumber alloc] initWithInt:secs];
NSDictionary *myDict = [NSDictionary dictionaryWithObject:elapsedSeconds forKey:#"TotalSeconds"];
gameTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(echoIt:) userInfo:myDict repeats: YES];
}
}
-(void)echoIt: (NSTimer *) timer {
NSNumber *num = (NSNumber *) [[timer userInfo] valueForKey:#"TotalSeconds"];
seconds++;
NSInteger sec = [num integerValue] - seconds;
NSInteger minutes = sec / 60;
[gameTimeLabel setText:[NSString stringWithFormat:#"%02i:%02i", minutes, sec-(60*minutes)]];
if (sec == 0) {
[self playSound:#"Horn"];
[gameTimer invalidate];
}
}
NSNumber *elapsedSeconds = [[NSNumber alloc] initWithInt:secs];
In general dont alloc NSNumbers and a NSDictionary (and NSArray,NSSet) always retains the objects it is given (and releases them too when it should).
try...
NSNumber *elapsedSeconds = [NSNumber numberWithInt:secs];
It'll stop the retain cycle you've got and might stop the crash.
Also irrelevant but stylewise.
NSInteger secs = [[[timer userInfo] valueForKey:#"TotalSeconds"] intValue];
Is a little more efficient.
Related
I am new to IOS.I want to user countdown timer.I mean If it set timer for 5 min then after 5 min a function should call so I am using NSTimer for that and I will show min and seconds in a label so after 5 min the label will start from 0 min so it is perfectly working when my app is in foreground when when my app go to background NSTimer will not work after 3 min so when ever I am going I am background I am save time and second in NSUserDefault and I am assigning that values to label when it comes to foreground but the timer is not showing perfectly
-(void) StartTimer
{
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerTick:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}
- (void)timerTick:(NSTimer *)timer
{
timeSec++;
if (timeSec >= 60)
{
timeSec = timeSec-60;
timeMin++;
}
// else
// {
// [self StopTimer];
// }
//Format the string 00:00
NSString* timeNow = [NSString stringWithFormat:#"%02d:%02d", timeMin, timeSec];
//Display on your label
//[timeLabel setStringValue:timeNow];
self.lblForTimer.text= timeNow;
NSNumber *numberValue = [NSNumber numberWithInt:[user.timerInterval intValue]];
if (timeMin>= [numberValue intValue])
{
timeMin = 0;
timeSec++;
}
}
//Call this to stop the timer event(could use as a 'Pause' or 'Reset')
- (void) StopTimer
{
[timer invalidate];
timer=nil;
timeSec = 0;
timeMin = 0;
//Since we reset here, and timerTick won't update your label again, we need to refresh it again.
//Format the string in 00:00
NSString* timeNow = [NSString stringWithFormat:#"%02d:%02d", timeMin, timeSec];
//Display on your label
// [timeLabel setStringValue:timeNow];
self.lblForTimer.text= timeNow;
}
- (void)appWillEnterForeground:(NSNotification *)notification {
NSLog(#"will enter foreground notification");
}
- (void)applicationDidEnterBackground:(NSNotification *)notification {
NSLog(#"did enter backgroun notification");
NSLog(#"%d,%d",timeMin,timeSec);
NSDate *currentDate= [NSDate date];
[[NSUserDefaults standardUserDefaults] setObject:currentDate forKey:#"backgroundDate"];
[[NSUserDefaults standardUserDefaults] setInteger:timeSec forKey:#"sec"];
[[NSUserDefaults standardUserDefaults] setInteger:timeMin forKey:#"min"];
[[NSUserDefaults standardUserDefaults] synchronize];
[self StopTimer];
}
- (void)appDidBecomeActive:(NSNotification *)notification {
NSDate *dateWhenAppGoesBg= (NSDate *)[[NSUserDefaults standardUserDefaults] objectForKey:#"backgroundDate"];
NSTimeInterval timeSpentInBackground = [[NSDate date] timeIntervalSinceDate:dateWhenAppGoesBg];
int timeMinBack=floor(timeSpentInBackground/60);
int timeSecBack=round(timeSpentInBackground - timeMinBack * 60);
NSLog(#"%d%d",timeMinBack,timeSecBack);
// timeMin=(int)[[NSUserDefaults standardUserDefaults] integerForKey:#"min"]+timeMinBack;
timeMin=[user.counterValue intValue];
// timeSec=(int)[[NSUserDefaults standardUserDefaults] integerForKey:#"sec"]+timeSecBack;
if (timeMin>0) {
timeSec=timeSecBack;
}
else
{
timeSec=(int)[[NSUserDefaults standardUserDefaults] integerForKey:#"sec"]+timeSecBack;
}
NSLog(#"timme sec %d",timeSec);
[self StartTimer];
}
this is my code for implementing countdown timer its perfectly working in foreground but when it comes to forground from background timer values is nit perfect there is 1 min of gap between timer cananyone help me out in this issue
You can save currentDate as property. Don't need to use NSUserDefaults in this case.
Save and manipulate your timeout just in seconds and when needed retranslate as so:
NSInteger min = self.timeout / 60;
NSInteger sec = self.timeout % 60;
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How to make a periodic call to a method in objective c?
I am making an app where when the user touches the screen, the following method is called:
- (void)explode:(int)x
The user only has to touch the screen once, but I want the method to be called repeatedly every 0.1 seconds for 100 times, and then it should stop being called.
Is there a way of setting up a 'temporary' timer like this on a method where an integer is being passed?
You could pass a counter and the 'x' as into the timer's userInfo. Try this:
Create the timer in the method that's catching the touch event and pass a counter and the int 'x' into the userInfo:
NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] initWithCapacity:2];
[userInfo setValue:[NSNumber numberWithInt:x] forKey:#"x"];
[userInfo setValue:[NSNumber numberWithInt:0] forKey:#"counter"];
[NSTimer timerWithTimeInterval:0.1
target:self
selector:#selector(timerMethod:)
userInfo:userInfo
repeats:YES];
Create the timer method, check the userInfo's number count, and invalidate the timer after 100 times:
- (void)timerMethod:(NSTimer *)timer
{
NSMutableDictionary *userInfo = timer.UserInfo;
int x = [[userInfo valueForKey:#"x"] intValue];
// your code here
int counter = [[userInfo valueForKey:#"counter"] intValue];
counter++;
if (counter >= 100)
{
[timer invalidate];
}
else
{
[userInfo setValue:[NSNumber numberWithInt:x] forKey:#"x"];
[userInfo setValue:[NSNumber numberWithInt:counter] forKey:#"counter"];
}
}
Please also see the Apple docs on NSTimer:
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/nstimer_Class/Reference/NSTimer.html
I have this code to change the number in the NSString every five seconds.
How will I keep the numbers running in a loop? It now runs from 1 to 19 ,and stops at the last one (19) with a SIGABRT on the line: label.text = ...
How can I start with the first number displayed (0), before the first timer fires?
Here is the code:
-(IBAction) rotate3
{
NSString *number = [self.dayArray description];
NSArray *array = [[NSArray alloc] initWithObjects: #"0", #"1", #"2",..., #"19",nil];
number = #"0" ;
numberCount++ ;
self.dayArray = array;
[array release];
label.text = [NSString stringWithFormat:#"Day: %# ", [dayArray objectAtIndex :numberCount ]];
}
//and the timer
- (void)viewDidLoad
{
timer=[NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:#selector(rotate3 )userInfo:nil repeats:YES];
}
Here is my answers:
1)I think, at the last one (19), the numberCount is 20 (numberCount++ ;).
2)Just set the value before scheduling the timer.
add this to your .h file, in your interface (that is, if it isn't already there)
{
NSInteger numberCount
}
Then in your viewDidLoad method, initialize numberCount and the label:
numberCount = 0;
label.text = #"0";
And in your time method, replace:
numberCount++
with
if(numberCount++ > 19)
numberCount = 0;
What is the "number" NSString used for, b.t.w.?
Why have dayArray?
why not something like
label.text = [NSString stringWithFormat:#"Day: %d", numberCount++];
if (numberCount>19) numberCount = 0;
I don't know what you have number count initialized to it should probably be -1 and also reinitialized to -1 . if you wish to iterate thru "Day: 0" ... "Day: 19"
It's not clear.
You could change the -(void)viewDidLoad timer to not repeat
timer=[NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:#selector(rotate3 )userInfo:nil repeats:NO];
then, conditionally set it up again in the rotate3 method if you still want to change the text 5 seconds later.
try this:
#pragma mark - timer callback
-(IBAction)rotate3
{
[label1 setText:[dayArray objectAtIndex:numberCount]];
numberCount++;
if (numberCount >= [dayArray count])
numberCount = 0;
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
dayArray = [[NSArray alloc] initWithObjects:#"0",#"1",#"2",#"3", nil];
[label1 setText:[dayArray objectAtIndex:0]];
numberCount = 1;
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(rotate3) userInfo:nil repeats:YES];
// Do any additional setup after loading the view, typically from a nib.
}
I have written the program, which chooses a random element from the array.
Once I press the button "Start", how do I loop that code?
I would like, that once a button "Start" is pressed, a new element from the array is chosen and written in the text field every 5 seconds.... Thanks for the answer.
#implementation MARandom
- (IBAction)Start:(id)sender {
NSArray *tones;
tones = [NSArray arrayWithObjects: #"F#0", #"Gb0", #"G0", #"G#0",#"Ab0",#"A0",#"A#0",#"Bb0",#"B0",
#"C1",#"C#1",#"Db1",#"D1",#"D#1",#"Eb1",#"E1",#"F1",#"F#1",#"Gb1",#"G1",#"G#1",#"Ab1",#"A1",#"A#1",#"Bb1",#"B1",
#"C2",#"C#2",#"Db2",#"D2",#"D#2",#"Eb2",#"E2",#"F2",#"F#2",#"Gb2",#"G2",#"G#2",#"Ab2",#"A2",#"A#2",#"Bb2",#"B2",
#"C3",#"C#3",#"Db3",#"D3",#"D#3",#"Eb3",nil];
i= (arc4random() % 48);
NSString *Tone;
Tone = [tones objectAtIndex: i];
[TextField setStringValue:(NSString *)Tone];
}
Please try this:
- (IBAction)Start:(id)sender {
[NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:#selector(updateTextFieldWithRandomNumber)
userInfo:nil
repeats:YES];
}
-(void)updateTextFieldWithRandomNumber{
NSArray *tones;
tones = [NSArray arrayWithObjects: #"F#0", #"Gb0", #"G0", #"G#0",#"Ab0",#"A0",#"A#0",#"Bb0",#"B0",
#"C1",#"C#1",#"Db1",#"D1",#"D#1",#"Eb1",#"E1",#"F1",#"F#1",#"Gb1",#"G1",#"G#1",#"Ab1",#"A1",#"A#1",#"Bb1",#"B1",
#"C2",#"C#2",#"Db2",#"D2",#"D#2",#"Eb2",#"E2",#"F2",#"F#2",#"Gb2",#"G2",#"G#2",#"Ab2",#"A2",#"A#2",#"Bb2",#"B2",
#"C3",#"C#3",#"Db3",#"D3",#"D#3",#"Eb3",nil];
i= (arc4random() % 48);
NSString *Tone;
Tone = [tones objectAtIndex: i];
[TextField setStringValue:(NSString *)Tone];
}
and also consider preparing the array at one place rather than the code that will be repeatedly called, perhaps at viewDidLoad.
Note: untested code, but should work.
OK, I have a memory management problem that is driving me up a wall. At one point I swear this worked with no problems, but now it's leaking memory everywhere and I can't figure out why.
To begin with I'm starting an NSTask and then running a loop while the task is running.
NSTask *encodingTask = [[NSTask alloc] init];
NSFileHandle *taskStdout = [NSFileHandle fileHandleForWritingAtPath:encodingOutput];
[encodingTask setStandardOutput:taskStdout];
[encodingTask setStandardError:taskStdout];
NSString argString = [NSString stingWithString: #"some arguments"];
[encodingTask setArguments:taskArgs];
[encodingTask setLaunchPath:somePath];
[encodingTask launch];
while ([encodingTask isRunning]){
sleep(1);
[self encodeProgressTimer];
}
The encodeProgessTimer method is grabbing the last line from the stdOut and placing that in the menu bar:
- (void)encodeProgressTimer
{
if ([[NSUserDefaults standardUserDefaults] boolForKey:#"menuProgress"]) {
// Read the last line
NSString *fileData = [NSString stringWithContentsOfFile:encodingOutput encoding:NSASCIIStringEncoding error:nil];
NSArray *lines = [fileData componentsSeparatedByString:#"\r"];
NSString *lastLine = [lines objectAtIndex:[lines count] - 1];
NSString *percent;
NSString *eta;
BOOL dataFound = NO;
if ([lastLine length] == 71) {
dataFound = YES;
percentRange = (NSRange) {23,5};
etaRange = (NSRange) {61,9};
percent = [lastLine substringWithRange:percentRange];
eta = [lastLine substringWithRange:etaRange];
}
else if ([lastLine length] == 72) {
dataFound = YES;
percentRange = (NSRange) {23,5};
etaRange = (NSRange) {62,9};
percent = [lastLine substringWithRange:percentRange];
eta = [lastLine substringWithRange:etaRange];
}
else if ([lastLine length] == 70) {
dataFound = YES;
percentRange = (NSRange) {23,5};
etaRange = (NSRange) {60,9};
percent = [lastLine substringWithRange:percentRange];
eta = [lastLine substringWithRange:etaRange];
}
if (dataFound) {
NSMutableString *bottomStr = [[NSMutableString alloc]
initWithFormat:#"Encoding: %#%% - ETA %#", percent, eta];
[appDelegate setMenuTop:topString andBottom:bottomStr];
[bottomStr release];
}
}
}
It's my understanding that anything I'm not specifically allocating and initializing should be auto released when the method has completed, but that isn't the case. Memory usage goes up exponentially every second when this is called. If I look at my memory allocations the number of living CFstings goes through the roof. If I turn of encodeProgressTimer my problems go away. I tried adding an autorelease pool to encodeProgressTimer which made memory usage very stable, however after 20 minutes or so of running I get a EXC_BAD_ACCESS. Turning on Zombies turns that into:
*** -[NSConcreteAttributedString _drawCenteredVerticallyInRect:scrollable:]: message sent to deallocated instance 0x2bc756e0
I actually went through and changed each variable declaration into it's alloc/init counterpart and manually released them, but that didn't solve the problem either. At this point I'm pretty stumped.
Also for the sake of completeness the [appDelegate setMenuTop: andBottom:] method looks like this:
-(void) setMenuTop: (NSString *) top andBottom: (NSString *) bottom
{
if ([[NSUserDefaults standardUserDefaults] boolForKey:#"menuProgress"]) {
[statusItem setImage:nil];
NSMutableParagraphStyle *lineHeight = [[NSMutableParagraphStyle alloc] init];
[lineHeight setMaximumLineHeight:10.5];
[lineHeight setLineBreakMode:NSLineBreakByTruncatingMiddle];
OperationQueue *opQueue = [OperationQueue sharedQueue];
NSString *sBuffer = [[NSMutableString alloc] initWithFormat: #"%# (%i More)\n%#", top, [opQueue queueCount] - 1, bottom];
attributes = [[NSDictionary alloc] initWithObjectsAndKeys:[NSFont menuFontOfSize:9], NSFontAttributeName, lineHeight, NSParagraphStyleAttributeName, nil];
if (statusTitle)
[statusTitle release];
statusTitle = [[NSAttributedString alloc] initWithString: sBuffer attributes: attributes];
[statusItem setAttributedTitle: statusTitle];
[lineHeight release];
[sBuffer release];
[attributes release];
}
}
There will be loads of stuff in the autorelease pool, but you need to explicitly drain it for the memory to go away. Change your while loop as follows:
while ([encodingTask isRunning]){
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
sleep(1);
[self encodeProgressTimer];
[pool drain];
}
Other stuff: if you are running this on a thread, you can't update user interface items directly. You need to use something like performSelectorOnMainThread: to actualy update the UI. If you are not running this on a thread, you need to rethink your design. The whole UI of your application will freeze while the loop is running.
You may want to use properties here or nil out the reference.
if (statusTitle) {
[statusTitle release];
statusTitle = nil;
}