Not calling [super dealloc] inside a static UIViewController subclass - objective-c

In my app I have a UIViewController subclass (VC for short) that I only use in one place in the entire app. In that place, I have been creating and pushing it like this:
MyViewController* VC = [MyViewController new];
[self.navigationController pushViewController:VC animated:YES];
[VC release];
but I was thinking that since this is the only place I am using a view controller of this type, I could do something like this so the settings used won't be reset each time the view controller is pushed onto the stack:
static MapsToSendPicker* VC = nil;
if(!VC) {
VC = [MapsToSendPicker new];
}
[self.navigationController pushViewController:VC animated:YES];
[VC release];
The problem with that code is that in VC's dealloc method, I release all of my instance variables and set them to nil, and finally I call [super dealloc]. This deallocates the static view controller, but the test if(!VC) isn't evaluated to true after (this would defeat the purpose of the whole idea if it were; then I'd have to recreate the view controller each time anyway).
My solution is overriding the dealloc method in MyViewController and not calling [super dealloc] at the end. This works, but the compiler raises a warning. How can I get rid of that warning while maintaining the functionality I gain with this design? Thanks!
Edit:
After a quick Google search, I have found this solution:
- (void)dealloc {
if(NO) {
[super dealloc];
}
}
but I would like something a little bit... cleaner. Any thoughts?

Remove the [VC release]; line and add [super dealloc] back. Everything will work properly and dealloc will never get called. Generally you should consider using NSUserDefaults in order to restore the VC properties instead of keeping the controller in memory all the time.

Related

UIViewController not being released when popped

I have a table view that when a cell is selected it pushes a view controller onto the navigation stack:
SAPostTableViewController *postViewController = [[SAPostTableViewController alloc] initWithNibName:NSStringFromClass([SAPostTableViewController class]) bundle:nil];
postViewController.site = site;
[self.navigationController pushViewController:postViewController animated:YES];
[postViewController release];
SAPostTableViewController has a static tableView which, and it's cells, are loaded from a nib.
I have overridden the initWithNibName:bundle: method:
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
self.sections = [NSMutableDictionary dictionary];
}
return self;
}
sections is a retained property.
In viewDidLoad of SAPostTableViewController I have this:
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(cellVisibiltyChanged:) name:#"SAStaticCellVisibiltyChanged" object:nil];
}
and so to match in viewDidUnload:
- (void)viewDidUnload
{
[super viewDidUnload];
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"SAStaticCellVisibiltyChanged" object:nil];
}
However when I press the back button in the navigation bar (all standard behaviour, no override) and SAPostTableViewController is popped, it doesn't call viewDidUnload or dealloc. So this means that if I then reselect the cell that pushes SAPostTableViewController it creates a new instance of SAPostTableViewController and repeating this back and forward just means the memory usage keeps increasing as the popped SAPostTableViewControllers never get deallocated. (I know this by running Instruments on allocations)
The weird thing is that if I release SAPostTableViewController twice then it works as I'd expect:
SAPostTableViewController *postViewController = [[SAPostTableViewController alloc] initWithNibName:NSStringFromClass([SAPostTableViewController class]) bundle:nil];
postViewController.site = site;
[self.navigationController pushViewController:postViewController animated:YES];
[postViewController release];
[postViewController release];
(If I add a third release statement, it crashes as I'd expect it to with just 2)
I have resorted to using retainCount and stepped through the lines of code the are executed in the first line of the directly above code, the retainCount remains at 1. It jumps up between the first and second line, so I can't see anywhere it is being retain an extra time?
The SAPostTableViewController is only used in this place, it is not a delegate of anything, nor does it have a delegate.
How can I find a fix, or is it something simple I've missed?
Here is what Instruments shows after pushing SAPostTableViewController just once (with only one release statement):
And what it shows after navigating back and forth repeatedly (again, one release statement):
You are creating a new Object when you clic one Cell, why you don't create your Object (SAPostTableViewController) in the init Method and then Push the same object watch time you click in Cell
you can do something like this :
postViewController = [[SAPostTableViewController alloc] initWithNibName:NSStringFromClass([SAPostTableViewController class]) bundle:nil];
and in the
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
postViewController.site = site;
[self.navigationController pushViewController:postViewController animated:YES]; [postViewController release];
}
I don't know what your problem is, but here are some things to consider:
You should absolutely NOT release the view controller twice. If you have indeed discovered a memory leak in UIKit (which is unlikely), then it is likely it would be fixed in a future version of UIKit. That means that anyone running an old version of your app on a new version of the operating system would experience nothing but crashes (due to over-releasing the view controller). It is better to leak than to crash. A leaky app is still a usable app (at least until you leak too much). But a crashing app can't be run at all.
-viewDidUnload is not doing what you think it should be doing. It is only called when the view controller's view is unloaded due to memory pressure. It is not called during normal deallocation. It would be wiser to rely on -viewWillAppear: and -viewDidDisappear: instead.

UItableView is not refreshing data after loading for second time the View

I'm having some trouble with a UITableView. It loads perfectly the first time I open it, but after the second try its not getting into the viewDidLoad method which causes the data not be refreshed on my tableview. I also made the proper release on my dealloc method. Any ideas about this? I've looked thru google but I didnt get anything useful. Thanks a lot for all the help you can provide me!
Here are my viewDidLoad and viewDidUnload methods
- (void)viewDidLoad
{
[super viewDidLoad];
saved_news.rowHeight =85;
addButton = [[UIBarButtonItem alloc] initWithTitle:#"Edit" style:UIBarButtonItemStyleBordered target:self action:#selector(edititems:)];
[self.navigationItem setLeftBarButtonItem:addButton];
[self setSaved:[CoreData sharedInstance].selectItems];
[saved_news reloadData];
}
- (void)viewDidUnload
{
[super viewDidUnload];
saved = nil;
saved_news = nil;
addButton =nil;
}
viewDidLoad and viewDidUnload are typically called only once during the lifetime of a controller (unless there's a low memory situation). I think what you are looking to use here are the viewDidAppear: and viewDidDisappear: methods.
Call
- (void)viewWillAppear:(BOOL)animated;
- (void)viewDidAppear:(BOOL)animated;
as you require load table here
[Yourtablename reloaddata];
Hope this helps..

Dealloc Being Called Twice?

Resloved!
Thanks to Lone Gunman, this issue was due to an oversight of not setting the many delegates to nil before releasing them.
This is a strange one... I'm familiar with basic memory management but I think something is unusual about what I am seeing. Here is a little background...
I have a NavigationController that handles the navigation between the following ViewControllers:
Home -> Games -> Game
When running the code it falls down when leaving the Game. Within the GameViewController there is a dealloc method that resembles:
- (void)dealloc
{
[board release];
[opponentsViewController release];
[instructionsViewController release];
[imgPicker release];
[gameView release];
[super dealloc];
}
When the navigation controller goes back to the Games list (from the Game) it throws a EXC_BAD_ACCESS. So I bring up my trusty profiler and check for Zombies. Alas, just as I expected a message is being sent to a deallocated object! Digging deeper I find there to be 3 entries in the object's history:
Board getting alloc'd (called by Game's init method)
Board getting released (called by Game's dealloc method)
Board being Zombie'd (called by Game's dealloc method)
Both calls 2 and 3 are called from UINavigationController setDisappearingViewController.
In my dealloc method I set breakpoints to each release call, when doing so - the [board release] call occurs, then the [opponentsViewController release] call occurs then the [board release] call occurs again. So I'm seeing the dealloc method does not finish completely and calls again.
What might be causing this?
Edit: This is the GameViewController Implementation
Code from the Games controller that adds this game:
-(void) gotoGame:(int)tag {
game = [[GameViewController alloc] init];
[self.navigationController pushViewController:game animated:YES];
[game release];
}
Edit: This is the GameViewController Header
I would try setting all your ivar's delegates to nil (EDIT: in dealloc). I've had a similar problem with a fetched results controller. Failed to set the its delegate to nil in dealloc and the core data stack still had a pointer to it when the view controller was released.
So that's my bet, set ivar delegates to nil in dealloc, although I can't see your header to know what protocols your are conforming to be sure.
EDIT: Explanation
Setting a delegate is actually giving the object that is doing the delegation a pointer (I believe it usually an assigned property).
#property (assign) delegate;
I'll use the problem I had as an example.
So let's say you have a view controller that has a fetchedResultsController as an ivar. When you set the FRCs delegate:
fetchedResultsController.delegate = self;
and the view controller gets released, any object that is using that pointer still thinks it's live. You would think since the FRC is getting released in dealloc as well, you'd be fine(which is why it took me 4 days to figure this out :) ), but sometimes other parts of an implementation use your delegate as well. So the fix is:
-(void)dealloc
{
self.fetchedResultsController.delegate = nil;
[_fetchedResultsController release];
[super dealloc];
}
Note: as soon as the new tools are available to everyone you won't have to worry about this stuff anymore ^ ^;
try
- (void) dealloc {
if(game != nil){
//release here
[game release];
}
[super dealloc];
}
By the way it seems you have declare game in header file and just after pushing you are releasing it and also in dealloc method you are releasing it. Either remove the release call from dealloc method or change you method like this.
-(void) gotoGame:(int)tag {
GameViewController *game = [[GameViewController alloc] init];
[self.navigationController pushViewController:game animated:YES];
[game release];
}
UPDATE
Also you are not using the tag anywhere. Why don't you create your init method like this
GameViewController *game = [[GameViewController alloc] initWithTag:tag];
[self.navigationController pushViewController:game animated:YES];
[game release];

Why release a property that you've already set to nil?

Here are two methods in a view controller from an Apple tutorial:
- (void)viewDidUnload {
self.eventsArray = nil;
self.locationManager = nil;
self.addButton = nil;
}
- (void)dealloc {
[managedObjectContext release];
[eventsArray release];
[locationManager release];
[addButton release];
[super dealloc];
}
Couldn't the dealloc method be shortened to the following? If not, why not?
- (void)dealloc {
[managedObjectContext release];
[super dealloc];
}
- (void)viewDidUnload is not guaranteed to be called, so you should always release things in dealloc too.
See this question to find out when it's called, and what you should do when it is.
No, because you cannot rely on viewDidUnload being called upon deallocation. viewDidUnload is only called when the view controller receives a memory warning while its view is not on screen. If the view controller gets deallocated, viewDidUnload is not called (AFAIK, I'm not entirely sure).
because it's a good practice to always cleanup your ivars in dealloc. something could go wrong, or you may encounter an execution you do not expect.
Setting eventsArray to nil just means it has no content, but still space for content
Calling [eventsArray release] releases the space the array consumed.

viewDidDisappear not getting called on a UINavigationController

I'm having a problem with my navigation controller. If I add a view controller to the stack:
- (void) tui_ToggleListStudy:(id)sender
{
listVC = [[ListViewController alloc] init];
[self.navigationController pushViewController:listVC animated:NO];
[listVC release];
}
I have NSLog messages for the view controller beneath, for both viewWillDisappear: and viewDidDisappear - but only viewWillDisappear: is getting called.
Not only that, but the view controller doesn't receive any other delegate messages either: No viewDidUnload, or dealloc...
Is there anything I can do about this?
I'm stumped! Any thoughts?
Thanks!
I know the answer if you made the same typo in your code that you made in your question: the method signature is viewDidDisappear: (with the animated argument), not viewDidDisappear.
Not only that, but the view controller doesn't receive any other delegate messages either: No viewDidUnload, or dealloc...
A view controller will not be deallocated when you push another controller onto the stack. And viewDidUnload won't be called unless you run out of memory.
Assuming your navigation controller is contained in some kind of top view controller, you must also forward the relevant messages from that top view controller to the nav controller:
-(void)viewWillAppear:(BOOL)animated
{
[navController viewWillAppear:animated];
}
-(void)viewDidAppear:(BOOL)animated
{
[navController viewDidAppear:animated];
}
-(void)viewWillDisappear:(BOOL)animated
{
[navController viewWillDisappear:animated];
}
-(void)viewDidDisappear:(BOOL)animated
{
[navController viewDidDisappear:animated];
}
You must call super at implementation of viewWillDisappear.
The designated initializer for UIViewController is -initWithNibName:bundle:. Are you sure your view controller is finding its nib and finds its connected view? I'll bet if you set a breakpoint after init'ing your ListViewController, you'll find its -view returns nil.