I have a large hierarchy of view/viewcontrollers.
In the main controller I have the following code where aViewController is a member of MyClass:
#implementation MyClass
...
- (void) viewDidLoad
{
[self.view addSubview:aViewController_.view];
[aViewController_ setDataSource:self];
[aViewController_ setDelegate:self];
}
- (void)dealloc
{
//[aViewController_.view removeFromSuperview]; // All ok when this is added
[aViewController_ release];
[super dealloc];
}
...
#end
When running this, I see that aViewController is never released - retain count remains 1 at the end.
If howeevr I add [aViewController_.view removeFromSuperview]; to dealloc the everything works fine.
Why is this? Shouldn't [super dealloc] take care of the release of the view? Does it matter that the view is being released after the controller?
I have tried to reproduce with a simple test application without any luck.
This is adding your view to be a subview of aViewController_.view.
[self.view addSubview:aViewController_.view];
aViewController_.view is retaining the view so when you release your main controller the view does not get deallocated that is correct.
You need to remove the view from the superview before you deallocate your "main controller".
The "main controller" will be gone when you release it but the super view still has retention of the subview.
You can do this somewhere in the aViewController_ when your finished with the view before you release the controller that owns the view.
Its hard to say without seeing all your code.
Just remember that when you add the view to another view that the super view retains the view until it is removed.
Related
I have aUITabBarController as my main base view controller. Under the first tab, I have a UINavigationController which of course has a rootViewController associated with it, call it vcA. vcA has a button which fires a child view controller, vcB using the code:
[self performSegueWithIdentifier:#"PlacesDetailsSegue" sender:senderDictionary];
This appears to work, and I see in instruments that new allocations for vcB are occurring.
When I pop the view controller back to vcA, everything looks to work, but it appears that vcB is never released (i.e., dealloc is never called). So every time a go from vcA->vcB->vcA->vcB, the memory usage increases and increases.
I have some instance variables inside vcB, all of which I set to nil in dealloc. But since dealloc isn't being fired, they are never actually set to nil.
I do have a [UIView animationWith...] block which references the frame properties of self.view, but I have managed that using the code:
__unsafe_unretained BBCategoryViewController *weakSelf = self;
[UIView animateWithDuration:duration
animations:^{
[_feedTableView setFrame:CGRectMake(0,
_navBar.frame.size.height,
weakSelf.view.frame.size.width,
weakSelf.view.frame.size.height - kMenuBarHeight)
];
}
completion:nil];
Does anyone have any idea how I can go about finding out what objects are still being retained on a vcB pop?
For reference, my interface extension is:
#interface BBCategoryViewController () <UITableViewDataSource, UITableViewDelegate> {
UITableView *_feedTableView;
UIRefreshControl *_refreshControl;
BBSearchBar *_searchBar;
MKMapView *_mapView;
BBNavigationBar *_navBar;
NSString *_title;
NSString *_categoryId;
NSArray *_feedArray;
}
#end
and my dealloc (which is never fired) is:
-(void)dealloc {
NSLog(#"Dealloc: BBCategoryViewController\n");
_feedTableView = nil;
_refreshControl = nil;
_searchBar = nil;
_mapView = nil;
_navBar = nil;
_feedArray = nil;
}
UPDATE: This was actually due to a retain cycle in a child view, and not directly related to the view controllers as I first thought. Dan F led me to the correct answer here, in case anyone runs across something similar.
Your vcB should absolutely be deallocated, and dealloc called when you pop back to vcA. You must either be keeping a strong reference to it somewhere, or you're doing your "pop" incorrectly. If you're doing that with a segue, that could be your problem -- segues should not be used for going backwards (except unwind segues). So either use an unwind segue or use popViewControllerAnimated to go back to vcA. Also, when using ARC, there's no need to set your ivars to nil in dealloc.
As far as I understand the SDK documentation UIViewController's navigationItem lifecycle is bound to the controller itself and not to the controller's view. I.e. in the default implementation it is created on-demand and destroyed with the view controller - with all contents like button items and titleView. Given that both button items and the titleView may be represented by UIView instances - does that mean that once created these views will stay in memory until controller is destroyed and live through all memory warnings?
What is the sense behind this design decision? Is impact for memory usage considered too small to bother? Is it really small for an application which is using customized nav bar buttons/titles everywhere?
It is easy to explicitly bound some of the navigationItem properties to the controller's view lifecycle - like setting titleView in -viewDidLoad and dropping it in -viewDidUnload (self.navigationItem.titleView = nil). But the navigationItem property documentation suggests to avoid this pattern. Are there any other potential problems other than the given example with back button?
Added a category (snippet2) to track the retain count and the destruction of the navigation items, feel free to do the same :) Seems like it is not deallocated with the memory warning. An explanation would come from a common sense that view controllers don't have to be used with the navigation controller: that should be why the nav-item is added with a separate category (snippet1) and it's lifecycle must be managed with a nav-controller, not the view controller instance itself.
In the case the custom nav-items are so heavy that you need to release it whenever possible,
i would leave the default implementation, add custom nav-items category and manage this items manually as i wish (again through overriding required UINavigationController methods like nav-controllers didReceiveMemoryWarning, pushViewController:animated:, popViewControllerAnimated:animated:). I can't imagine such a case when it is really needed however.
snippet 1
#interface UIViewController (UINavigationControllerItem)
#property(nonatomic,readonly,retain) UINavigationItem *navigationItem; // Created on-demand so that a view controller may customize its navigation appearance.
#property(nonatomic) BOOL hidesBottomBarWhenPushed; // If YES, then when this view controller is pushed into a controller hierarchy with a bottom bar (like a tab bar), the bottom bar will slide out. Default is NO.
#property(nonatomic,readonly,retain) UINavigationController *navigationController; // If this view controller has been pushed onto a navigation controller, return it.
#end
snippet 2
#implementation UINavigationItem (Logs)
- (id)init
{
NSLog(#"I'm initialized (%#)", [self description]);
self = [super init];
return self;
}
-(void) release
{
NSLog(#"I'm released [%d](%#)", [self retainCount], [self description]);
[super release];
}
-(void) dealloc
{
NSLog(#"I'm deallocated [%d](%#)", [self retainCount], [self description]);
[super dealloc];
}
#end
Within my mainViewController I'm adding a view from another ViewController. I'm removing it's view when finished. What is the proper way to manage this and where do I release the view controller?
mainVc.m
-(void)showView {
helpPage *elementController = [[helpPage alloc] init];
[self.view addSubview:elementController.view];
}
helpPage.m
-(void)removeView {
[self.view removeFromSuperview];
}
Since you called alloc init on the controller in that instance of mainVc, that instance of mainVc owns releasing the controller.
When it calls addSubView, it will retain the view (and add to view hierarchy) and when it's removed from the superview, it will be released. At that point, when it's release, the controller that created the view is still retaining it as well. When both have released the view, it will go away.
Here's a related SO post:
Does UIView's addSubview really retain the view?
I think you are looking for something like this
mainVc.m
-(void)showView {
helpPage *elementController = [[helpPage alloc] init];
[self.view addSubview:elementController.view];
[elementController release];
}
helpPage.m
-(void)removeView {
[self.view removeFromSuperview];
}
Granted, I haven't seen the rest of your code, so that might not be what you are looking for. However, in terms of memory allocation, any time you alloc something, you must release it later. In the case of views and view controllers, once you add that view or view controller, you can release the copy you "alloc'ed."
I downloaded iAdSuite and looked into ADBannerNavigation.
Inside, I changed the RootViewController to subclass TextViewController in order to take advantage of the iAd banner resizing. I want to display ads on the RootView as well.
This is now RootViewController.h:
#import <UIKit/UIKit.h>
#import "TextViewController.h"
#interface RootViewController : TextViewController
#end
Everything else is the same. When I compile and run, no ads show up in RootView, and when I click into TextView, ads suddenly show up.
When I click to go back, there is now white space in RootView.
WHY?
How do you remove the white space?
Found the error in how I was removing the ADBannerView.
iAd Suite tells us to:
Note: If your application has multiple tabs or views displaying an iAd banner, be sure to share a single instance of ADBannerView across each view. Then, before your users navigate to a new view, set the shared instance’s delegate property to nil, remove it from the old view hierarchy, then add the same instance to the opening view and set its delegate to the appropriate view controller. The "AdBannerNavigation" sample shows this technique.
So, in my iADBannerView.m, I have:
- (void)viewWillDisappear:(BOOL)animated{
[self removeADBannerFromView];
[super viewWillDisappear:animated];
}
- (void)removeADBannerFromView{
NSLog(#"ad removed from view");
ADBannerView *adBanner = SharedAdBannerView;
adBanner.delegate = nil;
[adBanner removeFromSuperview];
}
- (void)dealloc{
// we are being called here when we navigate away from this view controller,
// so go ahead and reset our AdBannerView for the next time
//
ADBannerView *adBanner = SharedAdBannerView;
adBanner.delegate = nil;
[adBanner removeFromSuperview];
[contentView release]; contentView = nil;
[super dealloc];
}
By setting breakpoints, I saw that by exiting a view, viewWillDisappear was being called on view1, then viewWillAppear on view0 and then dealloc on view1.
The problem was that view1 already removed the ADBannerView from the view, so [adBanner removeFromSuperView] was removing the Ad from view0.
Problem solved by removing offending code from the dealloc method.
I have several buttons on my main UIViewController (main menu) that creates and adds a subview UIViewController on top of the main menu. When I remove the subview the memory from that controller is not released. How can I release that subviews memory instantly?
Does anyone have an example? This would solve all my problems! Thanks in advance.
Here is how I add a subview
if((UIButton *) sender == gameClassicBtn) {
GameClassic *gameClassicController = [[GameClassic alloc]
initWithNibName:#"GameClassic" bundle:nil];
self.gameClassic = gameClassicController;
[gameClassicController release];
[self.view insertSubview:gameClassicController.view atIndex:1];
}
Based on the code you provided there could be at least two places the UIViewController is being retained - one by the view heirarchy (self.view) and the other by a member variable (self.gameClassic). It sounds like you're only releasing the view heirarchy reference but not the member variable. If you release the latter does it deallocate the UIViewController?