I have a XIB with UIView.
This UIView is associated (in IB) with custom class of UIView - Page1.
File's Owner of this is MainMenuController (UIViewController, of course).
I use this XIB when init controller with "initWithNibName" and add it into navController.viewControllers.
In Page1.m I write:
- (void)didMoveToSuperview
{
NSLog(#"Page 1 did move to superview");
mainTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(refreshDateAndTime) userInfo:nil repeats:YES];
}
-(void) refreshDateAndTime {
NSLog(#"second");
}
- (void)dealloc
{
NSLog(#"Page1 dealloc called");
[mainTimer invalidate];
mainTimer = nil;
[mainTimer release];
[super dealloc];
}
When I start timer "mainTimer" by this code, method "dealloc" isn't called and object isn't unload from memory and timer is running.
If I comment lines in "didMoveToSuperview" block dealloc called and all is OK.
Why?
Dealloc will only be called when your object has no retains left (ie, after your final release).
When you create your timer, you are telling it that its target is self. To avoid complications later on (ie, having self be released from memory and your timer still active, it therefore retains self. Timers will retain their targets.
This means there is one extra retain on your view, which means dealloc won't be called (it's still retained by the timer).
Basically, don't use dealloc to invalidate your timer - use something else (perhaps a method that is triggered when your timer is no longer required).
Related
NB: To clarify what I'm trying to do here:
I have an instance of a subclass of UIView. When this instance gets released from a View Controller I would like that the dealloc method of that instance be called.
The point being to release other objects within that instance of the subclass of UIView.
In the View Controller:
- (void)viewDidLoad
{
[super viewDidLoad];
self.mav = [[MapAreaView alloc]init];
[self.view addSubview:self.mav];
[self.mav release];
t_ = [NSTimer scheduledTimerWithTimeInterval: 20.0f target: self selector:#selector(onTick) userInfo: nil repeats:NO];
}
and:
- (void)onTick
{
NSLog(#"Releasing...");
[t_ invalidate];
t_ = nil;
[self.mav release];
[self.mav release];
[self.mav release];
NSLog(#"Done releasing.");
}
In MapAreaView:
- (void)dealloc
{
NSLog(#"map view area dealloc called.");
[super dealloc];
}
I am trying to release the MapAreaView and have it's dealloc method called.
Also, when I run this app, Releasing... and Done releasing. get printed, but not map view area dealloc called. And despite all of my excessive release messages sent to self.mav the app doesn't crash. Weird.
Ideas?
EDIT 1
#interface MemoryManagmentViewController : UIViewController {
MapAreaView *mav_;
NSTimer *t_;
}
#property (nonatomic, retain) MapAreaView *mav;
which is then synthesized: #synthesize mav = mav_;
EDIT 2
Please not that the timer is not for use in my real application. I'm just using it to learn about memory management.
First off, don't use [self.mav release]. This is incorrect use of properties, and is not guaranteed to release your object. Use self.mav = nil instead.
Secondly, I presume your crazy timer is there just to try and force the release of your view and isn't really code you are using? Because it really shouldn't be there!
To manage the memory of a view that you want to both keep a pointer to and add as a subview to your main view, remove the release and timer messages from your viewDidLoad. You now have one retained pointer to mav, via your property, and the view will be retaining it as well since it is a subview.
In viewDidUnload, your view no longer exists so it will no longer have a pointer to mav. Therefore, if you put self.mav = nil there, mav will be released.
You shouldnt try to release it while it is still a subview of another view.
You don't get to choose when dealloc gets run*
You don't know what else in the iOS framework is retaining your map view.
You should think about memory management in terms of ownership - while you want an object, make sure you've retained it (either explicitly or as a property) and when you are finished with it, make sure you release it (etiher by calling release, autorelease or by setting your property to nil).
Your map view will get released eventually, don't try to force it!
(And it's been said in many other answers but it pretty important so here it is again - don't call self.property release, do self.property = nil; instead :)
*not entirely true for object in libraries you have written yourself but it's definitely true for objects from third party frameworks, including ones from Apple!
The line:
[self.view addSubview:self.mav];
will retain self.mav and should be paired by this line in onTick.
[self.mav removeFromSuperview];
I'm not sure why your over-releasing in onTick doesn't release self.mav anyway - but if you do weird stuff you can guarantee that weird stuff will happen.
Don't release it within onTick again. Do these two steps:
[self.mavview removeFromSuperView];
self.mavview = nil;
That's it. There won't be any more references, so it will be deallocated.
First and foremost thing is
NEVER call release on self.iVar
Donot user self.iVar while allocating.
These are the basic things in memory management.
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.
I'm pretty sure this is really simple, and I'm just missing something obvious. I have an app that needs to download data from a web service for display in a UITableView, and I want to display a UIAlertView if the operation takes more than X seconds to complete. So this is what I've got (simplified for brevity):
MyViewController.h
#interface MyViewController : UIViewController
<UITableViewDelegate, UITableViewDataSource> {
NSTimer *timer;
}
#property (nonatomic, retain) NSTimer *timer;
MyViewController.m
#implementation MyViewController
#synthesize timer;
- (void)viewDidLoad {
timer = [NSTimer scheduledTimerWithTimeInterval:20
target:self
selector:#selector(initializationTimedOut:)
userInfo:nil
repeats:NO];
[self doSomethingThatTakesALongTime];
[timer invalidate];
}
- (void)doSomethingThatTakesALongTime {
sleep(30); // for testing only
// web service calls etc. go here
}
- (void)initializationTimedOut:(NSTimer *)theTimer {
// show the alert view
}
My problem is that I'm expecting the [self doSomethingThatTakesALongTime] call to block while the timer keeps counting, and I'm thinking that if it finishes before the timer is done counting down, it will return control of the thread to viewDidLoad where [timer invalidate] will proceed to cancel the timer. Obviously my understanding of how timers/threads work is flawed here because the way the code is written, the timer never goes off. However, if I remove the [timer invalidate], it does.
I think there is a problem with scheduling a timer and doing a blocking call on the same thread. Until the blocking call is completed, the run-loop cannot fire the timer.
I suggest you to detach a thread to perform the long operation. Once the long operation is finished, call back on the main thread to invalidate the timer.
Note: it is important to invalidate the timer on the same thread it was scheduled.
- (void)viewDidLoad {
timer = [NSTimer scheduledTimerWithTimeInterval:20
target:self
selector:#selector(initializationTimedOut:)
userInfo:nil
repeats:NO];
[NSThread detachNewThreadSelector:#selector(doSomethingThatTakesALongTime:) toTarget:self withObject:nil];
}
- (void)doSomethingThatTakesALongTime:(id)arg {
sleep(30); // for testing only
// web service calls etc. go here
[self performSelectorOnMainThread:#selector(invalidate) withObject:nil waitUntilDone:NO];
}
- (void)invalidate {
[timer invalidate];
}
- (void)initializationTimedOut:(NSTimer *)theTimer {
// show the alert view
}
Have you tried to use [NSThread sleepforTimeInterval:30]; ?
The sleep() occurs on the main thread and the associated run loop never has the chance to invoke the selector for the timer.
If you would do real work in -doSomething that doesn't block the thread, e.g. non-blocking calls to web-services, it would work as expected. Blocking calls however would have to be done in a different thread so the main run loop does not get blocked.
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];
}
I have a line of UILabel text in a UIView which is regularly updated via NSTimer. This code is supposed to write a status item near the bottom of the screen every so often. The data comes from outside of its control.
My app runs out of memory really fast because it seems the UILabel is not being released. It seems that dealloc is never called.
Here is a very compressed version of my code (error checking etc removed for clarity.):
File:SbarLeakAppDelegate.h
#import <UIKit/UIKit.h>
#import "Status.h"
#interface SbarLeakAppDelegate : NSObject
{
UIWindow *window;
Model *model;
}
#end
File:SbarLeakAppDelegate.m
#import "SbarLeakAppDelegate.h"
#implementation SbarLeakAppDelegate
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
model=[Model sharedModel];
Status * st=[[Status alloc] initWithFrame:CGRectMake(0.0, 420.0, 320.0, 12.0)];
[window addSubview:st];
[st release];
[window makeKeyAndVisible];
}
- (void)dealloc
{
[window release];
[super dealloc];
}
#end
File:Status.h
#import <UIKit/UIKit.h>
#import "Model.h"
#interface Status : UIView
{
Model *model;
UILabel * title;
}
#end
File:Status.m
This is the where the problem lies. UILabel just does not seem to be released, and quite possible the string as well.
#import "Status.h"
#implementation Status
- (id)initWithFrame:(CGRect)frame
{
self=[super initWithFrame:frame];
model=[Model sharedModel];
[NSTimer scheduledTimerWithTimeInterval:.200 target:self selector:#selector(setNeedsDisplay) userInfo:nil repeats:YES];
return self;
}
- (void)drawRect:(CGRect)rect
{
title =[[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 12.0f)];
title.text = [NSString stringWithFormat:#"Tick %d", [model n]] ;
[self addSubview:title];
[title release];
}
- (void)dealloc
{
[super dealloc];
}
#end
File: Model.h (this and the next are the data sources, so included only for completeness.) All it does is update a counter every second.
#import <Foundation/Foundation.h>
#interface Model : NSObject
{
int n;
}
#property int n;
+(Model *) sharedModel;
-(void) inc;
#end
File: Model.m
#import "Model.h"
#implementation Model
static Model * sharedModel = nil;
+ (Model *) sharedModel
{
if (sharedModel == nil)
sharedModel = [[self alloc] init];
return sharedModel;
}
#synthesize n;
-(id) init
{
self=[super init];
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(inc) userInfo:nil repeats:YES];
return self;
}
-(void) inc
{
n++;
}
#end
The problem is that you are never removing the UILabel from the Status UIView. Let's take a look at your retain counts in drawRect:
(void)drawRect:(CGRect)rect {
title =[[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 12.0f)];
Here, you have created a UILabel with alloc, which creates an object with a retain count of 1.
[self addSubview:title];
[title release];
Adding the UILabel to Status view increases title's retain count to 2. The following release results in a final retain count of 1. Since the object is never removed from its superview, the object is never deallocated.
Essentially, you are adding one UILabel on top of another, each time the timer is fired, until memory runs out.
As suggested below, you should probably create the UILabel once when the view loads, and just update the UILabel's text with [model n].
As a housekeeping note, you might also want to make sure that you are properly deallocating any left over objects in your dealloc methods. 'model' and 'title' should be released in Status' dealloc, just as 'model' should be in SbarLeakAppDelegate.
Hope this helps.
Edit [1]:
It sounds like you have the memory issue pretty well handled at this point. I just wanted to suggest another alternative to the two timers you are using.
The timer you have running in your Status object fires every .2 seconds. The timer which actually increments the 'model' value, n, fires only once each second. While I believe you are doing this to ensure a more regular "refresh rate" of the Status view, you are potentially re-drawing the view 4 or 5 times per second without the data changing. While this is may not be noticeable because the view is fairly simple, you might want to consider something like NSNotification.
With NSNotification, you can have the Status object "observe" a particular kind of notification that will be fired by the Model whenever the value 'n' changes. (in this case, approximately 1 per second).
You can also specify a callback method to handle the notification when it is received. This way, you would only call -setNeedsDisplay when the model data was actually changed.
There are 2 problems with your code.
Problem 1
In -drawRect you add a subview to the view hierarchy every time the view is drawn. This is wrong for 2 reasons:
Every time the view is drawn, the number of subviews increases by 1
You are modifying the view hierarchy at draw time - this is incorrect.
Problem 2
Timers retain their targets. In the initializer for your Status object, you create a timer which targets self. Until the timer is invalidated, there is a retain cycle between the timer and the view, so the view will not be deallocated.
If the approach of using a timer to invalidate the view is really the correct solution to your problem, you need to take explicit steps to break the retain cycle.
One approach to doing this is to schedule the timer in -viewDidMoveToWindow: when the view is being put in a window [1], and invalidate the timer when the view is being removed from a window.
[1] Having the view invalidate itself periodically when it isn't displayed in any window is otherwise pointless.
Instead of calling -setNeedsDisplay with your NSTimer in the view controller, why not create a method that calls "title.text = [NSString stringWithFormat:#"Tick %d", [model n]] ;"? That way instead of re-creating the label every time the timer fires you can just update the value displayed.