while loop memory problem - iphone

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.... */
}

Related

Saving NSTimer's time to disk on entering background and retrieving on foreground

I want my timer to work in the background, and I have figured the only way to actually do that is to save the time on appliationDidEnterBackground and retrieve it with applicationDidLaunchWithOptions. My timer uses Core Data and my timeInterval (testTask.timeInterval) is saved after every decrement as so:
-(IBAction)startTimer:(id)sender{
if (timer == nil) {
[startButton setTitle:#"Pause" forState:UIControlStateNormal];
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerAction:) userInfo:nil repeats:YES];
} else {
[startButton setTitle:#"Resume" forState:UIControlStateNormal];
[timer invalidate];
timer = nil;
}
}
-(void)timerAction:(NSTimer *)t
{
if(testTask.timeInterval == 0)
{
if (self.timer)
{
[self timerExpired];
[self.timer invalidate];
self.timer = nil;
}
}
else
{
testTask.timeInterval--;
NSError *error;
if (![self.context save:&error]) {
NSLog(#"couldn't save: %#", [error localizedDescription]);
}
}
NSUInteger seconds = (NSUInteger)round(testTask.timeInterval);
NSString *string = [NSString stringWithFormat:#"%02u:%02u:%02u",
seconds / 3600, (seconds / 60) % 60, seconds % 60];
timerLabel.text = string;
NSLog(#"%f", testTask.timeInterval);
}
-(void)timerExpired{
UILocalNotification* localNotification = [[UILocalNotification alloc] init];
localNotification.alertBody = #"Time is up";
localNotification.alertAction = #"Ok";
localNotification.timeZone = [NSTimeZone defaultTimeZone];
[[UIApplication sharedApplication]presentLocalNotificationNow:localNotification];
}
These methods are in a detail view controller, which is initialized by this:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
DetailViewController *detailVC;
if (![self.detailViewsDictionary.allKeys containsObject:indexPath]){
detailVC = [[DetailViewController alloc]initWithNibName:#"DetailViewController" bundle:nil];
[self.detailViewsDictionary setObject:detailVC forKey:indexPath];
detailVC.context = self.managedObjectContext;
}else{
detailVC = self.detailViewsDictionary[indexPath];
}
Tasks *task = [[self fetchedResultsController] objectAtIndexPath:indexPath];
detailVC.testTask = task;
[[self navigationController] pushViewController:detailVC animated:YES];
NSLog(#"%#", self.detailViewsDictionary);
}
What I don't understand is...how would I access the timeInterval (each detailviewcontroller has a different timeinterval...) such that I can put it in appliationDidEnterBackground? Also, I am guessing I should save the time that the application enters the background and then save the time when it enters the foreground, and subtract? and then I would subtract that value from the timeinterval, correct?
First, you shouldn't be using a direct countdown like this if you care at all about accuracy. The NSTimer will not fire on exact one-second intervals, and delays will accumulate. The better way to do this is to create an NSDate when the timer starts, and get the NSTimeInterval since then at each tick, then calculate the minutes and seconds.
For each view controller to store its own start time, you can register any object to receive the UIApplicationDidEnterBackgroundNotification. This is posted at the same time that the app delegate gets applicationDidEnterBackground:.
Also, I am guessing I should save the time that the application enters the background and then save the time when it enters the foreground, and subtract? and then I would subtract that value from the timeinterval, correct?
Yes:
NSTimeInterval idleTime = [dateReturnedToForeground timeIntervalSinceDate:dateEnteredBackground];
NSTimeInterval elapsedTime = [[NSDate date] timeIntervalSinceDate:startDate];
elapsedTime -= idleTime;
will give you the active time that has elapsed for your timer.

Implementing a Countdown Timer in Objective-c?

I am new to iOS programming. I am working on words matching game. In this game I need to implement time counter which shows minutes and seconds. I want when my game is started my timer to start with 3 minutes. Now I want to decrease this timer in reverse direction with seconds. my code just work for seconds..here is my code:
secondsLeft--;
int seconds = (secondsLeft %3600) % 60;
Timerlbl.text = [NSString stringWithFormat:#"%02d",seconds];
if (seconds==0)
{
UIAlertView *pAlert = [[UIAlertView alloc]initWithTitle:#"Sorry!!"
message:#"TimeOver" delegate:self cancelButtonTitle:#"OK"
otherButtonTitles:#"Cancel",nil];
[pAlert show];
[pAlert release];
}
}
In Viewdidload i call it through timer..
countDown=[NSTimer scheduledTimerWithTimeInterval:5.0 target:self
selector:#selector(TimeOver) userInfo:nil repeats:YES];
pleas any one guide me how can i do it in both minutes and seconds.
You can do it like this(ARC Enabled):-
#interface ViewController()
{
UILabel *progress;
NSTimer *timer;
int currMinute;
int currSeconds;
}
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
progress=[[UILabel alloc] initWithFrame:CGRectMake(80, 15, 100, 50)];
progress.textColor=[UIColor redColor];
[progress setText:#"Time : 3:00"];
progress.backgroundColor=[UIColor clearColor];
[self.view addSubview:progress];
currMinute=3;
currSeconds=00;
// Do any additional setup after loading the view, typically from a nib.
}
-(void)start
{
timer=[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(timerFired) userInfo:nil repeats:YES];
}
-(void)timerFired
{
if((currMinute>0 || currSeconds>=0) && currMinute>=0)
{
if(currSeconds==0)
{
currMinute-=1;
currSeconds=59;
}
else if(currSeconds>0)
{
currSeconds-=1;
}
if(currMinute>-1)
[progress setText:[NSString stringWithFormat:#"%#%d%#%02d",#"Time : ",currMinute,#":",currSeconds]];
}
else
{
[timer invalidate];
}
}
I know this is old question, but I want to share my way to achieve this. We have method (timeIntervalSinceDate:) to calculate the interval and have fixed amount of seconds to count down. In my case 300 s.
UPDATE JULY, 2015 SWIFT
#IBAction func startTimer(sender: UIButton) {
sender.selected = !sender.selected;
//if selected fire timer, otherwise stop
if (sender.selected) {
self.timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: Selector("updateTimer"), userInfo: nil, repeats: true);
self.startDate = NSDate();
} else {
self.stopTimer();
}
}
func stopTimer() {
self.timer.invalidate();
}
func updateTimer() {
// Create date from the elapsed time
var currentDate:NSDate = NSDate();
var timeInterval:NSTimeInterval = currentDate.timeIntervalSinceDate(self.startDate!);
//300 seconds count down
var timeIntervalCountDown = 300 - timeInterval;
var timerDate:NSDate = NSDate(timeIntervalSince1970: timeIntervalCountDown);
// Create a date formatter
var dateFormatter = NSDateFormatter();
dateFormatter.dateFormat = "mm:ss";
dateFormatter.timeZone = NSTimeZone(forSecondsFromGMT: 0);
// Format the elapsed time and set it to the label
var timeString = dateFormatter.stringFromDate(timerDate);
self.timerLabel?.text = timeString;
}
Working github swift sample project
Objective-C
- (void)updateTimer
{
// Create date from the elapsed time
NSDate *currentDate = [NSDate date];
NSTimeInterval timeInterval = [currentDate
timeIntervalSinceDate:self.startDate];
NSLog(#"time interval %f",timeInterval);
//300 seconds count down
NSTimeInterval timeIntervalCountDown = 300 - timeInterval;
NSDate *timerDate = [NSDate
dateWithTimeIntervalSince1970:timeIntervalCountDown];
// Create a date formatter
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"mm:ss"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
// Format the elapsed time and set it to the label
NSString *timeString = [dateFormatter stringFromDate:timerDate];
self.stopWatch.text = timeString;
}
- (void)stopTimer
{
[self.stopWatchTimer invalidate];
self.stopWatchTimer = nil;
[self updateTimer];
}
- (void)startTimer
{
if (self.stopWatchTimer) {
[self.stopWatchTimer invalidate];
self.stopWatchTimer = nil;
}
self.startDate = [NSDate date];
// Create the stop watch timer that fires every 100 ms
self.stopWatchTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/10.0
target:self
selector:#selector(updateTimer)
userInfo:nil
repeats:YES];
}
UPDATE:
it is extremely expensive to create a NSDateFormatter each time you pass through updateTimer. Far better to create the NSDateFormatter outside the loop and reuse it. Credits: #siburb
In .h file
IBOutlet UILabel* lblTimer;
NSTimer* gameTimer;
In .m file
-(void)viewDidLoad
{
[super viewDidLoad];
[self createTimer];
}
- (void)createTimer
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
gameTimer = [[NSTimer timerWithTimeInterval:1.00 target:self selector:#selector(timerFired:) userInfo:nil repeats:YES] retain];
[[NSRunLoop currentRunLoop] addTimer:gameTimer forMode:NSDefaultRunLoopMode];
timeCount = 20;
[runLoop run];
[pool release];
}
- (void)timerFired:(NSTimer *)timer
{
if(timeCount == 0)
{
[self timerExpired];
}
else
{
timeCount--;
if(timeCount == 0) {
[timer invalidate];
[self timerExpired];
}
}
lblTimer.text = [NSString stringWithFormat:#"%02d:%02d",timeCount/60, timeCount % 60];
self.title = lblTimer.text;
}
Bind the above Label Outlet with Label in View.
It is working correctly with Me
Simple and less code in Objective-C,
in .h file create objects for Timer and totalSeconds
NSTimer *timer;
int totalSeconds;
.m file
//you can call this method where you want
[self startTimer];
-(void)startOtpTimer {
totalSeconds = 120;//your time
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self
selector:#selector(updateCountDownTime) userInfo:nil repeats:YES];
}
-(void)updateCountDownTime {
[self populateLabelwithTime:totalSeconds];
if( totalSeconds != 0) {
totalSeconds -= 1;
} else {
[timer invalidate];//stop timer
self.timeLabel.text = #"";//after reach 0 you can set empty string
}
}
- (void)populateLabelwithTime:(int)seconds {
self.timeLabel.text = [NSString stringWithFormat:#"%02d", totalSeconds];
}
You can use NSTimer to achieve this.

NSTimer Pause/ resume leak

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.

nstimer stop when touch the screen

I have a timer class, with a nstimer inside, who push notification to a uitableviewcontroller, and the notification object(value of the timer) is affected in a uitableviewcell. My problem is when I touch the screen to scroll, the timer stop, NSTimer don't start in a other thread? How I can fix this problem.
My timerClass
#import "timerEtape.h"
#import "FonctionUtile.h"
#import "Mission.h"
static timerEtape *sngTimerEtape = nil;
#implementation timerEtape
#synthesize repeatingTimerEtape;
#synthesize dateComp;
#synthesize questionCircuit;
#synthesize b_Pause;
+(timerEtape *) singletonTimer
{
#synchronized(self){
if (sngTimerEtape == nil )
{
sngTimerEtape = [[timerEtape alloc]init];
}
}
return sngTimerEtape;
}
-(id)init
{
self = [super init];
if (self != nil) {
dateComp = [[NSDateComponents alloc] init];
b_Pause = FALSE;
}
return self;
}
- (void)startTimer {
[repeatingTimerEtape invalidate];
NSString * temps;
if (questionCircuit) {
temps = [[Mission singletonMission].circuitTerrain valeurChampEtapeCircuitEnCours:#"et_temps_etape" :FALSE];
}
else {
temps = [[Mission singletonMission].histoireTerrain valeurChampQuestion:#"hi_temps_etape" :FALSE];
}
if (!b_Pause) {
[dateComp setHour:0];
[dateComp setMinute:[[FonctionUtile gauche:temps :2] intValue]];
[dateComp setSecond:[[FonctionUtile droite:temps :2] intValue]];
}
else {
b_Pause = FALSE;
}
self.repeatingTimerEtape = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(updateLabelTimerEtape:) userInfo:nil repeats:YES];
}
-(void)pause
{
self.b_Pause = TRUE;
[repeatingTimerEtape invalidate];
}
-(void)updateLabelTimerEtape:(NSTimer*)theTimer
{
if ([dateComp second] == 0) {
if ([dateComp minute] == 0) {
if ([dateComp hour] != 0) {
[dateComp setHour:[dateComp hour] -1];
[dateComp setMinute:59];
[dateComp setSecond:59];
}
else {
[repeatingTimerEtape invalidate];
[delegate performSelector:touchAction];
}
}
else {
[dateComp setMinute:[dateComp minute] -1];
[dateComp setSecond:59];
}
}
else {
[dateComp setSecond:[dateComp second] -1];
}
[[NSNotificationCenter defaultCenter] postNotificationName:#"rafraichirTimerEtape" object:[NSString stringWithFormat:#"%02d:%02d", [dateComp minute],[dateComp second]]];
}
-(void)setTarget:(id)target andAction:(SEL)action {
delegate = target;
touchAction = action;
}
-(void)dealloc
{
[dateComp release];
[repeatingTimerEtape release];
[super dealloc];
}
#end
and when I receive the notification in my uitableviewcontroller class
-(void)rafraichirTimerEtape:(NSNotification*)notification
{
[tempsRestant release];
tempsRestant = [[notification object]copy];
[table cellForRowAtIndexPath:[NSIndexPath indexPathForRow:2 inSection:0]].detailTextLabel.text = [notification object];
}
thanx
I have encountered with this problem before. In fact, NSTimer does not run in another thread, it always run in the thread which it is started. There is a runloop concept in iphone sdk, you should read it from here to understand timers. In this concept, you push some jobs into runloop and runloop implements them sequentially. The main thread is always a run loop and ui jobs are run there. So if you start your timer in the main thread, it will be affected by ui processing. You should start a new thread, configure it as a run loop and start your timers there.
Edit: Here is a sample code from the link that I posted. threadMain is the first function which the thread starts.
- (void) threadMain
{
NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
// Create and schedule the timer.
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self
selector:#selector(doFireTimer:) userInfo:nil repeats:YES];
NSInteger loopCount = 10;
do
{
// Run the run loop 10 times to let the timer fire.
[myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
loopCount--;
}
while (loopCount);
}
if you want runloop to run infinitely, but terminate it with a condition, use this instead
BOOL shouldKeepRunning = YES; // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self
selector:#selector(doFireTimer:) userInfo:nil repeats:YES];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]]);

NSArray Problem

i try to generate 6 random numbers and put them in a global NSArray, thats what i have done:
in MainViewController.h
NSArray * zufallsZahlen;
i have function to generate the Numbers:
- (NSArray *)generateNumbers {
NSMutableSet *s = [NSMutableSet set];
while([s count] < 6) {
NSNumber *z = [NSNumber numberWithUnsignedInteger:arc4random() % 46];
if(![s containsObject:z])
[s addObject:z];
}
NSArray *zahlen = [[s allObjects] sortedArrayUsingSelector:#selector(compare:)];
return zahlen;
}
Now in the ViewDidLoad:
zufallsZahlen = [self generateNumbers];
[NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:#selector(onTimer:) userInfo:nil repeats:YES];
If i NSLog my zufallsZahlen NSArray in the ViewDidLoad i get the Output i want:
(
2,
8,
13,
16,
27,
31
)
The onTimer function creates every 0.2 seconds a ball with the actual Number:
-(void)onTimer:(NSTimer*)timer {
if (indexBall > 6){
//some function
}
else {
[self crateBall:[zufallsZahlen objectAtIndex:indexBall] ballId:indexBall ballX:xCoord ballY:100];
[self rollBall:indexBall rollToY: 80];
indexBall+=1;
xCoord-=40;
NSLog(#"%#", zufallsZahlen);
}
And if i Nslog the Array in the onTimer function i get the fooling Output:
Japanese.lproj
EDIT:
in viewDidLoad simply retain the NSArray:
zufallsZahlen = [self generateNumbers];
[zufallsZahlen retain];
You didn't retain the array. Remember that methods like [NSSet sortedArrayUsingSelector:] return an array that is autoreleased.
Hope that helps.
You can pass zufallsZahlen in the timer:
zufallsZahlen = [self generateNumbers];
[NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:#selector(onTimer:) userInfo:zufallsZahlen repeats:YES];
- (void) onTimer:NSArray *zufallsZahlen {
// Do something with zufallsZahlen
}