leak is raising in NSTimer - iphone

I am using a sub class in my application, in which i am using nstimer to detect user idle/inactivity.
Here is a leak and it raised on every tap.
here is code of my class
- (void)sendEvent:(UIEvent *)event {
[super sendEvent:event];
isRootView=FALSE;
// Only want to reset the timer on a Began touch or an Ended touch, to reduce the number of timer resets.
NSSet *allTouches = [event allTouches];
if ([allTouches count] > 0) {
// allTouches count only ever seems to be 1, so anyObject works here.
UITouchPhase phase = ((UITouch *)[allTouches anyObject]).phase;
if (phase == UITouchPhaseBegan)
[self resetIdleTimer];
}
}
- (void)resetIdleTimer {
if (idleTimer)
{
if ([idleTimer isValid])
{
[idleTimer invalidate];
//[idleTimer release];
//idleTimer=nil;
}
}
maxIdleTime = 60;
if (!isRootView)
{
idleTimer = [NSTimer scheduledTimerWithTimeInterval:maxIdleTime target:self selector:#selector(idleTimerExceeded) userInfo:nil repeats:NO];
[idleTimer retain];
}
else {
if ([idleTimer isValid])
{
[idleTimer invalidate];
//[idleTimer release];
//idleTimer = nil;
}
if ([resetTimer isValid]) {
[resetTimer invalidate];
resetTimer=nil;
}
}
}
- (void)idleTimerExceeded {
alert=[[UIAlertView alloc] initWithTitle:#"Confirmation!" message:#"Would you like to continue placing the order ?" delegate:self cancelButtonTitle:#"NO" otherButtonTitles:#"YES", nil];
alert.tag=100;
[alert show];
[alert release];
resetTimer=[NSTimer scheduledTimerWithTimeInterval:maxIdleTime target:self selector:#selector(resetApplication) userInfo:nil repeats:NO] ;
}
-(void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
if (alertView.tag==100 ) {
if (buttonIndex==1)
{
if ([resetTimer isValid]) {
[resetTimer invalidate];
resetTimer=nil;
}
}
else {
[self resetApplication];
}
}
}
-(void) resetApplication
{
isRootView=TRUE;
[alert dismissWithClickedButtonIndex:1 animated:YES];
if ([resetTimer isValid])
{
[resetTimer invalidate];
resetTimer=nil;
}
if (idleTimer)
{
[idleTimer invalidate];
[idleTimer release];
idleTimer = nil;
}
SushiTeriaAppDelegate *appDelegate=(SushiTeriaAppDelegate*)[[UIApplication sharedApplication] delegate];
[appDelegate resetApp];
}
- (void)dealloc {
[super dealloc];
//[resetTimer release];
[alert release];
}
I have retain this timer. If dont retain then application get crashed.
Please guide me how to remove this leak
shivam

Use assigned property for timer variable.
ex.
NSTimer globalTimer;
#property (nonatomic, assigned) NSTimer globalTimer;
#synthesize globalTimer;
Now assign timer on this variable and use like self.globalTimer.
One thing is that always assign nil on variable after use. Do not release it another wise it will
crashed your application.

What is happening is, you are calling the resetIdleTimer method every time the user taps on the screen. This increases the retain count of the idleTimer every time because you have retained it. The reason your app crashes if you don't retain is because you get an autoreleased NSTimer object from NSTimer class method
idleTimer=[NSTimer scheduledTimerWithTimeInterval:maxIdleTime target:self selector:#selector(idleTimerExceeded) userInfo:nil repeats:NO];
This means that you cannot use this object outside that particular method as you do in your resetApplication method. Now you could check the retain count every time you retain it but that is a hassle. So what I suggest is you declare idleTimer as a retain property.
#property (retain) NSTimer *idleTimer;
and synthesize its setters and getters after implementation.
#synthesize idleTimer;
now use
self.idleTimer=[NSTimer scheduledTimerWithTimeInterval:maxIdleTime target:self selector:#selector(idleTimerExceeded) userInfo:nil repeats:NO];
and release idleTimer only in your dealloc.

Related

NSTimer causes crash

The following method causes a crash. The UI is like a button, which handles the start / stop functionality of the NSTimer. If the timer runs, a UILabel is updated. Using the viewDidLoad Method makes my timer work, stopping it works too, but starting it again crashes the app.
Removing the alloc in the viewDidLoad method and trying to use the start button causes a crash instantly. Even the NSLog(#"Start now");is not called.
Code:
- (void)tick {
NSLog(#"tick");
float value = [moneyLabel.text floatValue];
moneyLabel.text = [NSString stringWithFormat:#"%f", value + 1.0];
}
- (IBAction)startStopButtonClicked:(UIButton *)sender {
if ([sender.titleLabel.text isEqualToString:#"Start"]) {
NSLog(#"Start now");
if (timer) {
NSLog(#"Timer valid");
[timer fire];
} else {
NSLog(#"Timer is nil");
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(tick) userInfo:nil repeats:YES];
[timer fire];
}
NSLog(#"bla");
[sender setTitle:#"Stop" forState:UIControlStateNormal];
} else {
[timer invalidate];
timer = nil;
NSLog(#"Stopped.");
NSLog(#"Timer isValid: %#", timer);
[sender setTitle:#"Start" forState:UIControlStateNormal];
}
}
I don't see the need to call [NSTimer fire] at all; it should be enough to allow the timer to decide when to fire.
Firstly ensure that timer is nil (it should be if it's an instance variable of the object), although explicitly setting it to nil in - (id)init won't hurt.
Next I would use the state of the timer itself to determine whether start/stop has been pressed, not the text in the button:
- (IBAction)startStopButtonClicked:(UIButton *)sender
{
if (timer != nil)
{
NSLog(#"Stopping timer");
[timer invalidate];
timer = nil;
}
else
{
NSLog(#"Starting timer");
timer = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:#selector(tick)
userInfo:nil
repeats:YES];
}
[sender setTitle:(timer != nil ? #"Stop" : #"Start")
forState:UIControlStateNormal];
}
The code you have posted works as desired - just tested it in a new project, so the problem could be somewhere else. I tested it only by declaring the ivar NSTimer *timer; without any initialization in viewDidLoad: or the designated initializers...

NSMutableArray elements not being released in dealloc

I have define a NSMutableArray in .h file
NSMutableArray *arrayBatLevel;
init it in .m file
- (void)viewDidLoad {
[super viewDidLoad];
[self DataTimer];
}
-(void)DataTimer
{
[recordDataTimer invalidate];
recordDataTimer = [NSTimer scheduledTimerWithTimeInterval:[timeInterval.text floatValue]
target:self
selector:#selector(recordData)
userInfo:nil
repeats:YES];
}
-(void)recordData
{
if ([aSwitch isOn] == YES) {
if (arrayBatLevel == nil) {
arrayBatLevel = [[NSMutableArray alloc] init];
NSLog(#"alloc arrayBatLevel");
}
[arrayBatLevel addObject:batLevel.text];
}
}
and release in dealloc
- (void)dealloc {
[arrayBatLevel release];
[super dealloc];
}
but, it seems it’s not releasing all the objects inside the NSMutableArray.
When I exit this app and run it gain, these objects still in NSMutableArray, why?
Your problem is not with the array, it's with the objects inside the array. You don't indicate where "batLevel" came from. I'm guessing that "batLevel" is retained somewhere else, and with it "batLevel.text".

Help changing NSTimer for textLabel. Code included

want to change my NSTimer code to to use applicationSignificantTimeChange
This is what I have right now.
Basically I want to change the timer, instead of changing every 5 seconds for example I want these textLabels to change at 12am every night anyone help me out?
//h file
NSTimer *timer;
IBOutlet UILabel *textLabel;
//m file
- (void)onTimer {
static int i = 0;
if ( i == 0 ) {
textLabel.text = #"iphone app";
}
else if ( i == 1 ) {
textLabel.text = #" Great App!";
}
else if ( i == 2 ) {
textLabel.text = #" WOW!";
}
else {
textLabel.text = #" great application again!!";
i = -1;
}
i++;
}
timer =[NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:#selector(onTimer) userInfo:nil repeats:YES];
There are two things you can do to respond to a applicationSignificantTimeChange:
(1) Just implement applicationSignificantTimeChange: in your app delegate if you have a connection to the view controller that should be updated.
- (void)applicationSignificantTimeChange:(UIApplication *)application {
yourViewController.textLabel.text = #"random text";
}
(2) Subscribe to the UIApplicationSignificantTimeChangeNotification notification in the view controller that should get the update. You could perhaps put that code in viewDidLoad
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(onSignificantTimeChange:)
name:UIApplicationSignificantTimeChangeNotification
object:nil];
}
- (void)onSignificantTimeChange:(NSNotification *)notification {
self.textLabel.text = #"random text";
}
You also need to call
[[NSNotificationCenter defaultCenter] removeObserver:self];
somewhere when your view controller is not longer needed.
the selector for the timer should be:
-(void) onTimer:(NSTimer *) timer
{
// yourcode
}
and called with:
[NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:#selector(onTimer:) userInfo:nil repeats:YES];
you need the "onTimer*:*"
[EDIT]
for calling a function that will go off at 12:am, for low-performance (i.e. not 12:00:00:0000) you could do the following for like an app that sits in the background:
NSTimeInterval minute = 60.0f
[NSTimer scheduledTimerWithTimeInterval:minute target:self selector:#selector(onTimer:) userInfo:nil repeats:YES]
-(void) onTimer:(NSTimer *) timer
{
NSDate *midnight = [NSDate dateWithNaturalLanguageString:#"today at 12:00 am"]; // unsure of this line
if ([midnight timeIntervalSinceDate:[NSDate date]] < 30.0f)
// set off alarm.
}
Seems pretty straightforward to me -- just create an NSDate for midnight, calculate the time interval of 24 hours in seconds, and create the NSTimer with
- (id)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats

NSTimer not invalidating or is refiring

so here is the problem i have, i created two NSTimer objects and they are fired when i button is pressed. then the user has 20 seconds to press another button which forces an alert to popup where they enter a validation code, and when they press the confirm button on the alert it is supposed to stop the timer. what is happening is that everything works until they press confirm but instead of the timer stopping it hangs for a second( which im thinking is a delay caused by the keyboard dismiss animation) and then the timer continues. any help would be greatly appreciated and here is all relevant code:
#import "hackergameViewController.h"
#import <AudioToolbox/AudioToolbox.h>
#implementation hackergameViewController
#synthesize decryptLabel, crackLabel, decryptButton, crackButton, submit, numberToDecrypt, numberToCrack, stopDecryptButton, stopCrackButton, inputCode;
#synthesize soundFileURLRefBeep;
#synthesize soundFileURLRefBuzz;
#synthesize soundFileObjectBeep;
#synthesize soundFileObjectBuzz;
NSTimer *decryptTimer;
NSTimer *crackTimer;
int crackTime;
int decryptTime;
NSString *codeToConfirm;
#pragma mark UIAlertView
- (void)confirm:(UIAlertView *)confirm clickedButtonAtIndex:(NSInteger)buttonIndex {
if(buttonIndex == 0){
[confirm dismissWithClickedButtonIndex:0 animated:YES];
}
else {
if (inputCode.text == codeToConfirm) {
[self stopCrack];
[self stopDecrypt];
}
}
}
-(void) generateDecryptionCode{
codeToConfirm = [NSString stringWithFormat:#"%i%i%i%i%i%i", arc4random() % 10,arc4random() % 10,arc4random() % 10,arc4random() % 10,arc4random() % 10,arc4random() % 10];
numberToDecrypt.text = codeToConfirm;
}
-(void) generateCrackCode{
codeToConfirm = [NSString stringWithFormat:#"%i%i%i%i%i%i%i%i%i%i", arc4random() % 10,arc4random() % 10,arc4random() % 10,arc4random() % 10,arc4random() % 10,arc4random() % 10,arc4random() % 10,arc4random() % 10,arc4random() % 10,arc4random() % 10];
numberToCrack.text = codeToConfirm;
}
- (void)dealloc {
[decryptLabel release];
[decryptButton release];
[crackLabel release];
[crackButton release];
[submit release];
[numberToCrack release];
[numberToDecrypt release];
[super dealloc];
}
- (void) confirmCode{
UIAlertView *confirm = [[UIAlertView alloc] initWithTitle:#"Confirm Code" message:#"Please Input The Correct Code:" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"Confirm Code", nil];
inputCode = [[UITextField alloc] initWithFrame:CGRectMake(12, 45, 260, 25)];
CGAffineTransform myTransform = CGAffineTransformMakeTranslation(0, 60);
[confirm setTransform:myTransform];
[inputCode setBackgroundColor:[UIColor whiteColor]];
[confirm addSubview:inputCode];
[confirm show];
[confirm release];
[inputCode release];
}
- (void) decryptTimerFires{
if(decryptTime > 0){
decryptTime--;
decryptLabel.text = [NSString stringWithFormat:#"%g", (float)decryptTime/10];
if(decryptTime%10 == 0){
AudioServicesPlaySystemSound (self.soundFileObjectBeep);
}
}
else{
[decryptTimer release];
decryptTimer = nil;
}
}
- (void) crackTimerFires{
if(crackTime > 0){
crackTime--;
crackLabel.text = [NSString stringWithFormat:#"%g", (float)crackTime/10];
if(crackTime%10 == 0){
AudioServicesPlaySystemSound (self.soundFileObjectBeep);
}
else if(crackTime == 0){
AudioServicesPlaySystemSound (self.soundFileObjectBuzz);
}
}
else{
[crackTimer release];
crackTimer = nil;
}
}
-(void) stopDecrypt{
[decryptTimer invalidate];
[decryptTimer release];
decryptTimer = nil;
}
-(void) stopCrack{
[crackTimer invalidate];
[crackTimer release];
crackTimer = nil;
}
-(IBAction)decrypt{
[self generateDecryptionCode];
decryptTime = 200;
decryptTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(decryptTimerFires) userInfo:nil repeats:YES];
[decryptTimer fire];
}
-(IBAction)crack{
[self generateCrackCode];
crackTime = 200;
crackTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(crackTimerFires) userInfo:nil repeats:YES];
[crackTimer fire];
}
In your confirm method, are you sure that your inputCode.text == codeToConfirm is returning true? Have you tried using [inputCode.text compare:codeToConfirm]? From what I can see, your timers only get invalidated in stopCrack and stopDecrypt, which only get called if that comparison succeeds.
First, you should not be calling "fire" manually unless you want the timer to fire immediately.
Second, you should call -invalidate rather than -release (as you're doing in your -decryptTimerFires and -crackTimerFires methods) when you want your timer to go away, then nil it out as you're doing. You're using the +scheduled... class method, which schedules the timer on the current run loop (which retains it, so it's not up to you to release it). -invalidate removes it from the run loop properly.
From the docs:
This method is the only way to remove
a timer from an NSRunLoop object.

iphone threads beginner question, having trouble with managing autorelease pool

I'm having trouble with a simple threading example. I know my methods aren't complete, but as of now, I want to press the start button and have that fire off a thread that increments a counter every second. I know it's connected in IB correctly since the NSLog tells me it gets to my timerThread method. But then it immediately jumps back to the initial myThread, without ever reaching the updateDisplay method and releases the pool, which is why I am guessing my program doesn't actually increment a counter. I thought I would then put it in a sleep interval or something, but in the end I think I am missing the correct way to achieve this. Any thoughts would be great. Thanks!
#implementation MainController
-(id)initWithLabel:(UILabel *)label {
if (self = [super init]) {
countLabel = label;
[countLabel retain];
}
return self;
}
-(int)count {
return count;
}
-(void)setCount:(int) value {
count = value;
}
-(void)updateDisplay:(NSTimer *)timer {
NSLog(#"%s", __FUNCTION__);
countLabel.text = [NSString stringWithFormat:#"%i", count];
count++;
}
-(void)timerThread {
NSLog(#"%s", __FUNCTION__);
[NSTimer timerWithTimeInterval:1.0
target:self
selector:#selector(updateDisplay:)
userInfo:nil
repeats:YES];
//NSNumber *threadID = [NSNumber numberWithInt:(int)threadID];
// threadLabel = [NSString stringWithFormat:#"%#", threadID];
}
-(void)myThread {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//NSNumber *threadID = [NSNumber numberWithInt:(int)threadID];
[self performSelectorOnMainThread:#selector(timerThread)
withObject:nil
waitUntilDone:NO];
// NSLog(#"threadID in myThread: %#", threadID);
[pool release];
}
-(void)startThread {
// threadIndex = 0;
// numThreads = 0;
// NSNumber *threadID = [NSNumber numberWithInt:threadIndex++];
[self performSelectorInBackground:#selector(myThread) withObject:nil];
// NSLog(#"%i", threadIndex);
numThreads++;
}
-(void)myThreadStop {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[NSThread exit];
[self performSelectorOnMainThread:#selector(updateDisplay)
withObject:nil
waitUntilDone:NO];
[pool release];
}
-(void)stopThread {
[self performSelectorInBackground:#selector(myThreadStop) withObject:nil];
}
-(void) dealloc {
[countLabel release];
[super dealloc];
}
#end
The short answer is that you did not schedule the timer.
iOS (and others) use run loops. Each thread may have a run loop, and the main UI thread has a run loop set up for you. Simplistically, a run loop keeps a queue of things to do and either does them in an ordered fashion or blocks until there is something to do.
Anything that you do with the active UI, like setting the text of a UILabel, must be done on the main thread. In your case, you have set up (but not scheduled) a timer on the main thread to update the timer. Scheduling a timer just means adding it to the run loop.
If you had a lengthy task to perform that would update the UI at the end, you could use performSelectorInBackground to start the lengthy task and performSelectorOnMainThread when the task finishes to update UI.
If you have a short periodic task, such as updating a clock or counter UI, you can just create an NSTimer on the same thread you want the timer to fire on. When creating the timer, use a scheduledTimerWithTimeInterval variant so it will start firing automatically.
When you create a repeating timer, you must keep a reference to it so you can invalidate the timer before the target is released. At the latest, in dealloc you should invalidate the timer.
Instead of calling startThread, turn timerThread into startTimer.
-(void) startTimer {
timerMember = [[NSTimer
scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(updateDisplay:)
userInfo:nil
repeats:YES] retain];
}
-(void) dealloc {
[timerMember invalidate];
[timerMember release];
...
}